# role-compile v1.5 — Compile 1C role from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] [string]$JsonPath, [Parameter(Mandatory)] [string]$OutputDir ) $ErrorActionPreference = "Stop" [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # --- 1. Load and validate JSON --- if (-not (Test-Path $JsonPath)) { Write-Error "File not found: $JsonPath" exit 1 } $json = Get-Content -Raw -Encoding UTF8 $JsonPath $def = $json | ConvertFrom-Json if (-not $def.name) { Write-Error "JSON must have 'name' field (role programmatic name)" exit 1 } $roleName = "$($def.name)" $synonym = if ($def.synonym) { "$($def.synonym)" } else { $roleName } $comment = if ($def.comment) { "$($def.comment)" } else { "" } # --- 2. XML helpers --- $script:xmlBuf = $null function X { param([string]$text) $script:xmlBuf.AppendLine($text) | Out-Null } function Esc-Xml { param([string]$s) return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"') } # --- 3. Russian synonyms → canonical English names --- $script:typeAliases = @{ "Справочник" = "Catalog" "Документ" = "Document" "РегистрСведений" = "InformationRegister" "РегистрНакопления" = "AccumulationRegister" "РегистрБухгалтерии" = "AccountingRegister" "РегистрРасчета" = "CalculationRegister" "Константа" = "Constant" "ПланСчетов" = "ChartOfAccounts" "ПланВидовХарактеристик" = "ChartOfCharacteristicTypes" "ПланВидовРасчета" = "ChartOfCalculationTypes" "ПланОбмена" = "ExchangePlan" "БизнесПроцесс" = "BusinessProcess" "Задача" = "Task" "Обработка" = "DataProcessor" "Отчет" = "Report" "ОбщаяФорма" = "CommonForm" "ОбщаяКоманда" = "CommonCommand" "Подсистема" = "Subsystem" "КритерийОтбора" = "FilterCriterion" "ЖурналДокументов" = "DocumentJournal" "Последовательность" = "Sequence" "ВебСервис" = "WebService" "HTTPСервис" = "HTTPService" "СервисИнтеграции" = "IntegrationService" "ПараметрСеанса" = "SessionParameter" "ОбщийРеквизит" = "CommonAttribute" "Конфигурация" = "Configuration" "Перечисление" = "Enum" # Nested "Реквизит" = "Attribute" "СтандартныйРеквизит" = "StandardAttribute" "ТабличнаяЧасть" = "TabularSection" "Измерение" = "Dimension" "Ресурс" = "Resource" "Команда" = "Command" "РеквизитАдресации" = "AddressingAttribute" } $script:rightAliases = @{ "Чтение" = "Read" "Добавление" = "Insert" "Изменение" = "Update" "Удаление" = "Delete" "Просмотр" = "View" "Редактирование" = "Edit" "ВводПоСтроке" = "InputByString" "Проведение" = "Posting" "ОтменаПроведения" = "UndoPosting" "ИнтерактивноеДобавление" = "InteractiveInsert" "ИнтерактивнаяПометкаУдаления" = "InteractiveSetDeletionMark" "ИнтерактивноеСнятиеПометкиУдаления" = "InteractiveClearDeletionMark" "ИнтерактивноеУдаление" = "InteractiveDelete" "ИнтерактивноеУдалениеПомеченных" = "InteractiveDeleteMarked" "ИнтерактивноеПроведение" = "InteractivePosting" "ИнтерактивноеПроведениеНеоперативное" = "InteractivePostingRegular" "ИнтерактивнаяОтменаПроведения" = "InteractiveUndoPosting" "ИнтерактивноеИзменениеПроведенных" = "InteractiveChangeOfPosted" "Использование" = "Use" "Получение" = "Get" "Установка" = "Set" "Старт" = "Start" "ИнтерактивныйСтарт" = "InteractiveStart" "ИнтерактивнаяАктивация" = "InteractiveActivate" "Выполнение" = "Execute" "ИнтерактивноеВыполнение" = "InteractiveExecute" "УправлениеИтогами" = "TotalsControl" "Администрирование" = "Administration" "АдминистрированиеДанных" = "DataAdministration" "ТонкийКлиент" = "ThinClient" "ВебКлиент" = "WebClient" "ТолстыйКлиент" = "ThickClient" "ВнешнееСоединение" = "ExternalConnection" "Вывод" = "Output" "СохранениеДанныхПользователя" = "SaveUserData" "МобильныйКлиент" = "MobileClient" } # Translate Russian object name to English (e.g. "Справочник.Контрагенты" → "Catalog.Контрагенты") function Translate-ObjectName { param([string]$name) $parts = $name.Split(".") $result = @() foreach ($p in $parts) { if ($script:typeAliases.ContainsKey($p)) { $result += $script:typeAliases[$p] } else { $result += $p } } return $result -join "." } # Translate Russian right name to English (e.g. "Чтение" → "Read") function Translate-RightName { param([string]$name) if ($script:rightAliases.ContainsKey($name)) { return $script:rightAliases[$name] } return $name } # --- 4. Known rights per object type (source: docs/1c-role-spec.md) --- $script:knownRights = @{ "Configuration" = @( "Administration","DataAdministration","UpdateDataBaseConfiguration", "ConfigurationExtensionsAdministration","ActiveUsers","EventLog","ExclusiveMode", "ThinClient","ThickClient","WebClient","MobileClient","ExternalConnection", "Automation","Output","SaveUserData","TechnicalSpecialistMode", "InteractiveOpenExtDataProcessors","InteractiveOpenExtReports", "AnalyticsSystemClient","CollaborationSystemInfoBaseRegistration", "MainWindowModeNormal","MainWindowModeWorkplace", "MainWindowModeEmbeddedWorkplace","MainWindowModeFullscreenWorkplace","MainWindowModeKiosk" ) "Catalog" = @( "Read","Insert","Update","Delete","View","Edit","InputByString", "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", "InteractiveDelete","InteractiveDeleteMarked", "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", "ReadDataHistory","ViewDataHistory","UpdateDataHistory", "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" ) "Document" = @( "Read","Insert","Update","Delete","View","Edit","InputByString", "Posting","UndoPosting", "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", "InteractiveDelete","InteractiveDeleteMarked", "InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting", "InteractiveChangeOfPosted", "ReadDataHistory","ViewDataHistory","UpdateDataHistory", "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" ) "InformationRegister" = @( "Read","Update","View","Edit","TotalsControl", "ReadDataHistory","ViewDataHistory","UpdateDataHistory", "UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData", "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" ) "AccumulationRegister" = @("Read","Update","View","Edit","TotalsControl") "AccountingRegister" = @("Read","Update","View","Edit","TotalsControl") "CalculationRegister" = @("Read","View") "Constant" = @( "Read","Update","View","Edit", "ReadDataHistory","ViewDataHistory","UpdateDataHistory", "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" ) "ChartOfAccounts" = @( "Read","Insert","Update","Delete","View","Edit","InputByString", "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", "InteractiveDelete", "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", "ReadDataHistory","ReadDataHistoryOfMissingData", "UpdateDataHistory","UpdateDataHistoryOfMissingData", "UpdateDataHistorySettings","UpdateDataHistoryVersionComment" ) "ChartOfCharacteristicTypes" = @( "Read","Insert","Update","Delete","View","Edit","InputByString", "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", "InteractiveDelete","InteractiveDeleteMarked", "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData", "ReadDataHistory","ViewDataHistory","UpdateDataHistory", "ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData", "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" ) "ChartOfCalculationTypes" = @( "Read","Insert","Update","Delete","View","Edit","InputByString", "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", "InteractiveDelete", "InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData", "InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData" ) "ExchangePlan" = @( "Read","Insert","Update","Delete","View","Edit","InputByString", "InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", "InteractiveDelete","InteractiveDeleteMarked", "ReadDataHistory","ViewDataHistory","UpdateDataHistory", "ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData", "UpdateDataHistorySettings","UpdateDataHistoryVersionComment", "EditDataHistoryVersionComment","SwitchToDataHistoryVersion" ) "BusinessProcess" = @( "Read","Insert","Update","Delete","View","Edit","InputByString", "Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", "InteractiveDelete","InteractiveActivate","InteractiveStart" ) "Task" = @( "Read","Insert","Update","Delete","View","Edit","InputByString", "Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark", "InteractiveDelete","InteractiveActivate","InteractiveExecute" ) "DataProcessor" = @("Use","View") "Report" = @("Use","View") "CommonForm" = @("View") "CommonCommand" = @("View") "Subsystem" = @("View") "FilterCriterion" = @("View") "DocumentJournal" = @("Read","View") "Sequence" = @("Read","Update") "WebService" = @("Use") "HTTPService" = @("Use") "IntegrationService" = @("Use") "SessionParameter" = @("Get","Set") "CommonAttribute" = @("View","Edit") } # Nested objects: Attribute, StandardAttribute, TabularSection, Dimension, Resource, AddressingAttribute $script:nestedRights = @("View","Edit") $script:commandRights = @("View") # --- 4. Presets (@view, @edit) --- $script:presets = @{ "view" = @{ "Catalog" = @("Read","View","InputByString") "ExchangePlan" = @("Read","View","InputByString") "Document" = @("Read","View","InputByString") "ChartOfAccounts" = @("Read","View","InputByString") "ChartOfCharacteristicTypes" = @("Read","View","InputByString") "ChartOfCalculationTypes" = @("Read","View","InputByString") "BusinessProcess" = @("Read","View","InputByString") "Task" = @("Read","View","InputByString") "InformationRegister" = @("Read","View") "AccumulationRegister" = @("Read","View") "AccountingRegister" = @("Read","View") "CalculationRegister" = @("Read","View") "Constant" = @("Read","View") "DocumentJournal" = @("Read","View") "Sequence" = @("Read") "CommonForm" = @("View") "CommonCommand" = @("View") "Subsystem" = @("View") "FilterCriterion" = @("View") "SessionParameter" = @("Get") "CommonAttribute" = @("View") "DataProcessor" = @("Use","View") "Report" = @("Use","View") "Configuration" = @("ThinClient","WebClient","Output","SaveUserData","MainWindowModeNormal") } "edit" = @{ "Catalog" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") "ExchangePlan" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") "Document" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Posting","UndoPosting","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting","InteractiveChangeOfPosted") "ChartOfAccounts" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") "ChartOfCharacteristicTypes" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") "ChartOfCalculationTypes" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark") "BusinessProcess" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveActivate","InteractiveStart") "Task" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveActivate","InteractiveExecute") "InformationRegister" = @("Read","Update","View","Edit") "AccumulationRegister" = @("Read","Update","View","Edit") "AccountingRegister" = @("Read","Update","View","Edit") "Constant" = @("Read","Update","View","Edit") "DocumentJournal" = @("Read","View") "Sequence" = @("Read","Update") "SessionParameter" = @("Get","Set") "CommonAttribute" = @("View","Edit") } } # --- 5. Helpers --- function Get-ObjectType { param([string]$objectName) $dotIdx = $objectName.IndexOf(".") if ($dotIdx -lt 0) { return $objectName } return $objectName.Substring(0, $dotIdx) } function Is-NestedObject { param([string]$objectName) return ($objectName.Split(".").Count -ge 3) } function Resolve-Preset { param([string]$objectType, [string]$presetName) $preset = $presetName.TrimStart('@') if (-not $script:presets.ContainsKey($preset)) { Write-Warning "Unknown preset '@$preset'. Known: @view, @edit" return @() } $typeMap = $script:presets[$preset] if (-not $typeMap.ContainsKey($objectType)) { $available = @() foreach ($k in $script:presets.Keys) { if ($script:presets[$k].ContainsKey($objectType)) { $available += "@$k" } } $availStr = if ($available.Count -gt 0) { $available -join ", " } else { "none" } Write-Warning "Preset '@$preset' not defined for type '$objectType'. Available: $availStr" return @() } return @($typeMap[$objectType]) } function Validate-RightName { param([string]$objectName, [string]$rightName) $objectType = Get-ObjectType $objectName if (Is-NestedObject $objectName) { if ($objectName -match '\.Command\.') { if ($rightName -notin $script:commandRights) { Write-Warning "${objectName}: '$rightName' not valid for commands (only: View)" return $false } } else { if ($rightName -notin $script:nestedRights) { Write-Warning "${objectName}: '$rightName' not valid for nested objects (only: View, Edit)" return $false } } return $true } if (-not $script:knownRights.ContainsKey($objectType)) { Write-Warning "${objectName}: unknown object type '$objectType'" return $true } $validRights = $script:knownRights[$objectType] if ($rightName -notin $validRights) { $suggestions = @($validRights | Where-Object { $_ -like "*$rightName*" -or $rightName -like "*$_*" }) $sugStr = if ($suggestions.Count -gt 0) { " Did you mean: $($suggestions -join ', ')?" } else { "" } Write-Warning "${objectName}: unknown right '$rightName'.$sugStr" return $false } return $true } # --- 6. Parse object entries --- function Parse-ObjectEntry { param($entry) # --- String shorthand --- if ($entry -is [string]) { $colonIdx = $entry.IndexOf(':') if ($colonIdx -lt 0) { Write-Warning "Invalid string '$entry' -- expected 'Object.Name: @preset' or 'Object.Name: Right1, Right2'" return $null } $objName = Translate-ObjectName ($entry.Substring(0, $colonIdx).Trim()) $rightsStr = $entry.Substring($colonIdx + 1).Trim() $objectType = Get-ObjectType $objName if ($rightsStr.StartsWith('@')) { $rightNames = @(Resolve-Preset -objectType $objectType -presetName $rightsStr) } else { $rightNames = @($rightsStr -split ',\s*' | ForEach-Object { Translate-RightName $_.Trim() } | Where-Object { $_ }) foreach ($r in $rightNames) { Validate-RightName -objectName $objName -rightName $r | Out-Null } } $rights = @() foreach ($r in $rightNames) { $rights += ,@{Name=$r; Value="true"; Condition=$null} } return @{ Name = $objName; Rights = $rights } } # --- Object form --- $objName = Translate-ObjectName "$($entry.name)" if (-not $objName) { Write-Warning "Object entry missing 'name' field" return $null } $objectType = Get-ObjectType $objName $rightsMap = [ordered]@{} # 1) Start with preset if ($entry.preset) { $presetRights = @(Resolve-Preset -objectType $objectType -presetName "$($entry.preset)") foreach ($r in $presetRights) { $rightsMap[$r] = @{Value="true"; Condition=$null} } } # 2) Apply explicit rights if ($entry.rights) { if ($entry.rights -is [array]) { foreach ($r in $entry.rights) { $rName = Translate-RightName "$r" Validate-RightName -objectName $objName -rightName $rName | Out-Null $rightsMap[$rName] = @{Value="true"; Condition=$null} } } else { foreach ($p in $entry.rights.PSObject.Properties) { $rName = Translate-RightName $p.Name Validate-RightName -objectName $objName -rightName $rName | Out-Null $boolVal = $p.Value if ($boolVal -eq $true -or "$boolVal" -eq "True") { $rightsMap[$rName] = @{Value="true"; Condition=$null} } else { $rightsMap[$rName] = @{Value="false"; Condition=$null} } } } } # 3) Apply RLS conditions if ($entry.rls) { foreach ($p in $entry.rls.PSObject.Properties) { $rlsRight = Translate-RightName $p.Name if ($rightsMap.Contains($rlsRight)) { $rightsMap[$rlsRight].Condition = "$($p.Value)" } else { Write-Warning "${objName}: RLS for '$rlsRight' but this right is not in the rights list" } } } # Convert to array $rights = @() foreach ($k in $rightsMap.Keys) { $rights += ,@{ Name = $k Value = $rightsMap[$k].Value Condition = $rightsMap[$k].Condition } } return @{ Name = $objName; Rights = $rights } } # --- 7. Parse all object entries --- # Synonym: accept "rights" as alias for "objects" if (-not $def.objects -and $def.rights) { $def | Add-Member -NotePropertyName objects -NotePropertyValue $def.rights } $parsedObjects = @() if ($def.objects) { foreach ($entry in $def.objects) { $parsed = Parse-ObjectEntry -entry $entry if ($parsed) { $parsedObjects += ,$parsed } } } # --- Detect format version --- function Detect-FormatVersion([string]$dir) { $d = $dir while ($d) { $cfgPath = Join-Path $d "Configuration.xml" if (Test-Path $cfgPath) { $head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length)) if ($head -match ']+version="(\d+\.\d+)"') { return $Matches[1] } } $parent = Split-Path $d -Parent if ($parent -eq $d) { break } $d = $parent } return "2.17" } $resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir } $formatVersion = Detect-FormatVersion $resolvedOutputDir # --- 8. Generate UUID --- $uuid = [guid]::NewGuid().ToString() # --- 9. Emit metadata XML (Roles/Name.xml) --- $script:xmlBuf = New-Object System.Text.StringBuilder 4096 X '' X '" X " " X ' ' X " $roleName" X ' ' X ' ' X ' ru' X " $(Esc-Xml $synonym)" X ' ' X ' ' if ($comment) { X " $(Esc-Xml $comment)" } else { X ' ' } X ' ' X ' ' X '' $metadataXml = $script:xmlBuf.ToString() # --- 10. Emit Rights XML (Roles/Name/Ext/Rights.xml) --- $script:xmlBuf = New-Object System.Text.StringBuilder 8192 X '' X '" # Global flags (defaults match typical 1C roles) $sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" } $sfab = if ($null -ne $def.setForAttributesByDefault) { "$($def.setForAttributesByDefault)".ToLower() } else { "true" } $irco = if ($null -ne $def.independentRightsOfChildObjects) { "$($def.independentRightsOfChildObjects)".ToLower() } else { "false" } X " $sfno" X " $sfab" X " $irco" # Object blocks $totalRights = 0 foreach ($obj in $parsedObjects) { X ' ' X " $($obj.Name)" foreach ($right in $obj.Rights) { X ' ' X " $($right.Name)" X " $($right.Value)" if ($right.Condition) { X ' ' X " $(Esc-Xml $right.Condition)" X ' ' } X ' ' $totalRights++ } X ' ' } # RLS restriction templates $templateCount = 0 if ($def.templates) { foreach ($tpl in $def.templates) { X ' ' X " $(Esc-Xml "$($tpl.name)")" X " $(Esc-Xml "$($tpl.condition)")" X ' ' $templateCount++ } } X '' $rightsXml = $script:xmlBuf.ToString() # --- 11. Write output files --- $outDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir } # Determine Roles dir and config root # Back-compat: if OutputDir leaf is "Roles", use as-is; otherwise treat as config root $leaf = Split-Path $outDir -Leaf if ($leaf -eq "Roles") { $rolesDir = $outDir $configDir = Split-Path $outDir -Parent } else { $rolesDir = Join-Path $outDir "Roles" $configDir = $outDir } # Metadata: Roles/RoleName.xml $metadataPath = Join-Path $rolesDir "$roleName.xml" if (-not (Test-Path $rolesDir)) { New-Item -ItemType Directory -Path $rolesDir -Force | Out-Null } # Rights: Roles/RoleName/Ext/Rights.xml $roleSubDir = Join-Path $rolesDir $roleName $extDir = Join-Path $roleSubDir "Ext" $rightsPath = Join-Path $extDir "Rights.xml" if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null } $enc = New-Object System.Text.UTF8Encoding($true) [System.IO.File]::WriteAllText($metadataPath, $metadataXml, $enc) [System.IO.File]::WriteAllText($rightsPath, $rightsXml, $enc) # --- 12. Register in Configuration.xml --- $configXmlPath = Join-Path $configDir "Configuration.xml" $regResult = $null if (Test-Path $configXmlPath) { $configDoc = New-Object System.Xml.XmlDocument $configDoc.PreserveWhitespace = $true $configDoc.Load($configXmlPath) $nsMgr = New-Object System.Xml.XmlNamespaceManager($configDoc.NameTable) $nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses") $childObjects = $configDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $nsMgr) if ($childObjects) { $existing = $childObjects.SelectNodes("md:Role", $nsMgr) $alreadyExists = $false foreach ($r in $existing) { if ($r.InnerText -eq $roleName) { $alreadyExists = $true break } } if ($alreadyExists) { $regResult = "already" } else { $roleElem = $configDoc.CreateElement("Role", "http://v8.1c.ru/8.3/MDClasses") $roleElem.InnerText = $roleName if ($existing.Count -gt 0) { # Insert after last existing $lastRole = $existing[$existing.Count - 1] $newWs = $configDoc.CreateWhitespace("`n`t`t`t") $childObjects.InsertAfter($newWs, $lastRole) | Out-Null $childObjects.InsertAfter($roleElem, $newWs) | Out-Null } else { # No existing roles — insert before closing whitespace $lastChild = $childObjects.LastChild if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) { $newWs = $configDoc.CreateWhitespace("`n`t`t`t") $childObjects.InsertBefore($newWs, $lastChild) | Out-Null $childObjects.InsertBefore($roleElem, $lastChild) | Out-Null } else { $childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t`t")) | Out-Null $childObjects.AppendChild($roleElem) | Out-Null $childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t")) | Out-Null } } # Save $cfgSettings = New-Object System.Xml.XmlWriterSettings $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) $cfgSettings.Indent = $false $stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create) $writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings) $configDoc.Save($writer) $writer.Close() $stream.Close() $regResult = "added" } } else { $regResult = "no-childobj" } } else { $regResult = "no-config" } # --- 13. Summary --- Write-Host "[OK] Role '$roleName' compiled" Write-Host " UUID: $uuid" Write-Host " Metadata: $metadataPath" Write-Host " Rights: $rightsPath" Write-Host " Objects: $($parsedObjects.Count), Rights: $totalRights, Templates: $templateCount" switch ($regResult) { "added" { Write-Host " Configuration.xml: $roleName added to ChildObjects" } "already" { Write-Host " Configuration.xml: $roleName already registered" } "no-childobj" { Write-Warning "Configuration.xml found but not found" } "no-config" { Write-Warning "Configuration.xml not found at $configXmlPath — register manually" } }