189 lines
9.6 KiB
PowerShell
189 lines
9.6 KiB
PowerShell
<#
|
||
.SYNOPSIS
|
||
Идемпотентно создаёт списки и библиотеки форм портала ct-forms-portal на целевом
|
||
сайте (/sites/sed) по манифестам provisioning.json.
|
||
|
||
.DESCRIPTION
|
||
Читает ВСЕ манифесты provisioning/manifests/*.provisioning.json (снятые
|
||
Export-ListSchema.ps1) и в ДВА прохода:
|
||
Проход A — создаёт все списки/библиотеки (если ещё нет) по всем манифестам.
|
||
Проход B — обеспечивает колонки на каждом списке.
|
||
Два прохода нужны, чтобы к моменту создания Lookup-колонки её целевой список уже
|
||
существовал (даже если он объявлен в другом манифесте — напр. общий «Поставщики»).
|
||
|
||
Литеральные имена: имя списка берётся как есть из манифеста (никаких конвенций
|
||
«— данные/— согласование», в отличие от request-portal). Поддержаны обычные списки
|
||
(GenericList) и библиотеки документов (DocumentLibrary).
|
||
|
||
Колонки создаются прямо НА СПИСКЕ (без общих столбцов сайта) — у форм CT/OID/KPS
|
||
имена колонок совпадают между списками, а Choice-наборы могут отличаться, поэтому
|
||
общий эталон сайта тут только мешал бы.
|
||
|
||
Повторный запуск безопасен: существующие списки/колонки не пересоздаются; для Choice
|
||
синхронизируются варианты.
|
||
|
||
Вход — по сертификату «от имени приложения» (app-only, Sites.Selected на /sites/sed).
|
||
|
||
.PARAMETER SiteUrl
|
||
Целевой сайт, например https://tnsukz.sharepoint.com/sites/sed
|
||
|
||
.PARAMETER ClientId
|
||
Client Id приложения-провижинера (Sites.Selected).
|
||
|
||
.PARAMETER Tenant
|
||
Тенант, например tnsukz.onmicrosoft.com
|
||
|
||
.PARAMETER Thumbprint
|
||
Отпечаток сертификата в хранилище CurrentUser\My.
|
||
|
||
.PARAMETER ManifestsDir
|
||
Папка с манифестами. По умолчанию — provisioning/manifests рядом со скриптом.
|
||
|
||
.PARAMETER Only
|
||
Провижионить только манифесты указанных форм (по полю form). По умолчанию — все.
|
||
|
||
.EXAMPLE
|
||
./Provision-Lists.ps1 -SiteUrl "https://tnsukz.sharepoint.com/sites/sed" `
|
||
-ClientId "<id>" -Tenant "tnsukz.onmicrosoft.com" -Thumbprint "<tp>"
|
||
#>
|
||
param(
|
||
[Parameter(Mandatory = $true)][string]$SiteUrl,
|
||
[Parameter(Mandatory = $true)][string]$ClientId,
|
||
[Parameter(Mandatory = $true)][string]$Tenant,
|
||
[Parameter(Mandatory = $true)][string]$Thumbprint,
|
||
[string]$ManifestsDir = "$PSScriptRoot/manifests",
|
||
[string[]]$Only
|
||
)
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
|
||
function ConvertTo-XmlSafe {
|
||
param([string]$Value)
|
||
return $Value.Replace('&', '&').Replace('<', '<').Replace('>', '>').Replace("'", ''').Replace('"', '"')
|
||
}
|
||
|
||
function Test-FieldExists {
|
||
param([string]$List, [string]$InternalName)
|
||
return $null -ne (Get-PnPField -List $List -Identity $InternalName -ErrorAction SilentlyContinue)
|
||
}
|
||
|
||
# Создаёт/обновляет колонку НА СПИСКЕ. Идемпотентно. Lookup/UserMulti/Number-с-decimals
|
||
# — через XML (иначе PnP-cmdlet не покрывает нюансы), остальное — обычным Add-PnPField.
|
||
function Add-ListColumn {
|
||
param([string]$List, $Col)
|
||
|
||
if (Test-FieldExists -List $List -InternalName $Col.internalName) {
|
||
if ($Col.type -eq 'Choice' -and $Col.choices) {
|
||
try {
|
||
$vals = @{ Choices = [string[]]$Col.choices }
|
||
if ($null -ne $Col.fillIn) { $vals.FillInChoice = [bool]$Col.fillIn }
|
||
Set-PnPField -List $List -Identity $Col.internalName -Values $vals | Out-Null
|
||
Write-Host " [~] варианты обновлены: $($Col.internalName)" -ForegroundColor DarkGray
|
||
}
|
||
catch { Write-Warning " [!] $($Col.internalName): не удалось обновить варианты — $($_.Exception.Message)" }
|
||
}
|
||
else { Write-Host " [=] есть: $($Col.internalName)" -ForegroundColor DarkGray }
|
||
return
|
||
}
|
||
|
||
try {
|
||
switch ($Col.type) {
|
||
'Text' { Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type Text -AddToDefaultView | Out-Null }
|
||
'Note' { Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type Note -AddToDefaultView | Out-Null }
|
||
'Boolean' { Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type Boolean -AddToDefaultView | Out-Null }
|
||
'DateTime' {
|
||
Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type DateTime -AddToDefaultView | Out-Null
|
||
if ($Col.dateOnly) { Set-PnPField -List $List -Identity $Col.internalName -Values @{ DisplayFormat = 0; FriendlyDisplayFormat = 1 } | Out-Null }
|
||
}
|
||
'Choice' {
|
||
$multi = [bool]$Col.multiChoice
|
||
$type = if ($multi) { 'MultiChoice' } else { 'Choice' }
|
||
Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type $type -Choices $Col.choices -AddToDefaultView | Out-Null
|
||
if ($Col.fillIn) { Set-PnPField -List $List -Identity $Col.internalName -Values @{ FillInChoice = $true } | Out-Null }
|
||
}
|
||
'Number' {
|
||
if ($null -ne $Col.decimals) {
|
||
$xml = "<Field Type='Number' DisplayName='$(ConvertTo-XmlSafe $Col.title)' Name='$($Col.internalName)' Decimals='$($Col.decimals)' />"
|
||
Add-PnPFieldFromXml -List $List -FieldXml $xml | Out-Null
|
||
}
|
||
else {
|
||
Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type Number -AddToDefaultView | Out-Null
|
||
}
|
||
}
|
||
'User' {
|
||
if ($Col.allowMultiple) {
|
||
$xml = "<Field Type='UserMulti' DisplayName='$(ConvertTo-XmlSafe $Col.title)' Name='$($Col.internalName)' Mult='TRUE' />"
|
||
Add-PnPFieldFromXml -List $List -FieldXml $xml | Out-Null
|
||
}
|
||
else {
|
||
Add-PnPField -List $List -InternalName $Col.internalName -DisplayName $Col.title -Type User -AddToDefaultView | Out-Null
|
||
}
|
||
if ($Col.userSelectionMode) {
|
||
$mode = if ($Col.userSelectionMode -eq 'PeopleAndGroups') { 1 } else { 0 }
|
||
Set-PnPField -List $List -Identity $Col.internalName -Values @{ SelectionMode = $mode } | Out-Null
|
||
}
|
||
}
|
||
'Lookup' {
|
||
$src = Get-PnPList -Identity $Col.lookupList -ErrorAction Stop
|
||
$guid = $src.Id.ToString()
|
||
$show = if ($Col.lookupShowField) { $Col.lookupShowField } else { 'Title' }
|
||
$multAttr = if ($Col.allowMultiple) { " Mult='TRUE'" } else { '' }
|
||
$ftype = if ($Col.allowMultiple) { 'LookupMulti' } else { 'Lookup' }
|
||
$xml = "<Field Type='$ftype' DisplayName='$(ConvertTo-XmlSafe $Col.title)' Name='$($Col.internalName)' ShowField='$show' List='{$guid}'$multAttr />"
|
||
Add-PnPFieldFromXml -List $List -FieldXml $xml | Out-Null
|
||
}
|
||
default { throw "Неизвестный тип колонки '$($Col.type)'" }
|
||
}
|
||
if ($Col.required -and $Col.type -ne 'Lookup') {
|
||
Set-PnPField -List $List -Identity $Col.internalName -Values @{ Required = $true } | Out-Null
|
||
}
|
||
Write-Host " [+] $($Col.internalName) — «$($Col.title)» [$($Col.type)]" -ForegroundColor Green
|
||
}
|
||
catch { Write-Warning " [!] $($Col.internalName): $($_.Exception.Message)" }
|
||
}
|
||
|
||
Write-Host "Подключение к $SiteUrl (app-only) ..." -ForegroundColor Cyan
|
||
Connect-PnPOnline -Url $SiteUrl -ClientId $ClientId -Tenant $Tenant -Thumbprint $Thumbprint
|
||
|
||
$files = Get-ChildItem -Path $ManifestsDir -Filter '*.provisioning.json' -ErrorAction SilentlyContinue
|
||
if (-not $files) { throw "Манифесты *.provisioning.json не найдены в $ManifestsDir" }
|
||
|
||
$manifests = @()
|
||
foreach ($file in $files) {
|
||
$def = Get-Content -Path $file.FullName -Raw -Encoding UTF8 | ConvertFrom-Json
|
||
if ($Only -and ($Only -notcontains $def.form)) { continue }
|
||
$manifests += $def
|
||
}
|
||
if (-not $manifests) { throw "Нет манифестов после фильтра -Only" }
|
||
|
||
# --- Проход A: создать все списки/библиотеки ---
|
||
Write-Host "`n=== Проход A: списки ===" -ForegroundColor Cyan
|
||
foreach ($def in $manifests) {
|
||
Write-Host "Форма '$($def.form)':" -ForegroundColor White
|
||
foreach ($ls in $def.lists) {
|
||
$tmpl = if ($ls.template -eq 'DocumentLibrary') { 'DocumentLibrary' } else { 'GenericList' }
|
||
if (Get-PnPList -Identity $ls.title -ErrorAction SilentlyContinue) {
|
||
Write-Host " [=] есть: «$($ls.title)»" -ForegroundColor DarkGray
|
||
}
|
||
else {
|
||
New-PnPList -Title $ls.title -Template $tmpl -EnableVersioning | Out-Null
|
||
Write-Host " [+] создан ($tmpl): «$($ls.title)»" -ForegroundColor Green
|
||
}
|
||
}
|
||
}
|
||
|
||
# --- Проход B: колонки ---
|
||
Write-Host "`n=== Проход B: колонки ===" -ForegroundColor Cyan
|
||
foreach ($def in $manifests) {
|
||
Write-Host "Форма '$($def.form)':" -ForegroundColor White
|
||
foreach ($ls in $def.lists) {
|
||
Write-Host " список «$($ls.title)»:" -ForegroundColor White
|
||
foreach ($col in $ls.columns) {
|
||
Add-ListColumn -List $ls.title -Col $col
|
||
}
|
||
}
|
||
}
|
||
|
||
Write-Host "`nГотово." -ForegroundColor Cyan
|
||
Disconnect-PnPOnline
|