[CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$SiteUrl, [Parameter(Mandatory = $true)] [string]$ListTitle, [Parameter(Mandatory = $true)] [string]$ExpiryFieldInternalName, [Guid]$FieldCustomizerComponentId = 'a555f4fc-d6a6-4421-8189-457449d9bbde', [string]$ClassicScriptUrl = '~sitecollection/SiteAssets/ExpiryIndicator/ExpiryIndicatorClassic.js', [switch]$SkipClassic ) $isapiPath = Join-Path ([Environment]::GetFolderPath('CommonProgramFiles')) 'microsoft shared\Web Server Extensions\16\ISAPI' Add-Type -Path (Join-Path $isapiPath 'Microsoft.SharePoint.Client.Runtime.dll') Add-Type -Path (Join-Path $isapiPath 'Microsoft.SharePoint.Client.dll') function Expand-ExpiryIndicatorBindings { param([object]$Value) if ($null -eq $Value) { return } if ($Value -is [System.Array]) { foreach ($item in $Value) { Expand-ExpiryIndicatorBindings -Value $item } return } if ($Value.PSObject.Properties['webUrl'] -and $Value.PSObject.Properties['listId'] -and $Value.PSObject.Properties['fieldInternalName']) { Write-Output $Value } } $context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl) $context.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials try { $list = $context.Web.Lists.GetByTitle($ListTitle) $field = $list.Fields.GetByInternalNameOrTitle($ExpiryFieldInternalName) $rootWeb = $context.Site.RootWeb $customActions = $list.UserCustomActions $webCustomActions = $context.Web.UserCustomActions $context.Load($list) $context.Load($field) $context.Load($context.Web) $context.Load($rootWeb) $context.Load($rootWeb.AllProperties) $context.Load($customActions) $context.Load($webCustomActions) $context.ExecuteQuery() if ($field.InternalName -ne $ExpiryFieldInternalName) { Write-Warning "Das Feld wurde über seinen Anzeigenamen gefunden. Verwende künftig den internen Namen '$($field.InternalName)'." } $alreadyBound = $field.ClientSideComponentId -eq $FieldCustomizerComponentId $field.ClientSideComponentId = $FieldCustomizerComponentId if (-not $alreadyBound -or [string]::IsNullOrWhiteSpace($field.ClientSideComponentProperties)) { $field.ClientSideComponentProperties = ([ordered]@{ schemaVersion = 2 expiryField = $field.InternalName inheritSiteDefaults = $true } | ConvertTo-Json -Compress) } $field.Update() $descriptorKey = 'PortalSettings.Solutions.ExpiryIndicator' $bindingsKey = 'PortalSettings.ExpiryIndicator.Bindings' $rootWeb.AllProperties[$descriptorKey] = ([ordered]@{ key = 'ExpiryIndicator' displayName = 'Expiry Indicator' version = '2.1.2' provider = 'expiryIndicator' componentIds = @( 'a555f4fc-d6a6-4421-8189-457449d9bbde', 'cd58f5d9-ffc8-4df7-a910-3054842d7abf', '95691218-bcb1-4fad-adda-02bb12014d15' ) settingsScope = @('siteCollection', 'web', 'list') } | ConvertTo-Json -Compress -Depth 5) $bindings = @() $existingBindingsJson = [string]$rootWeb.AllProperties[$bindingsKey] if (-not [string]::IsNullOrWhiteSpace($existingBindingsJson)) { try { $parsedBindings = $existingBindingsJson | ConvertFrom-Json $bindings = @(Expand-ExpiryIndicatorBindings -Value $parsedBindings) } catch { Write-Warning 'Das vorhandene ExpiryIndicator-Bindungsinventar war ungültig und wird neu aufgebaut.' } } $webUrl = $context.Web.Url.TrimEnd('/') $listId = $list.Id.ToString('D') $bindings = @($bindings | Where-Object { -not ( ([string]$_.webUrl).TrimEnd('/').Equals($webUrl, [StringComparison]::OrdinalIgnoreCase) -and ([string]$_.listId).Equals($listId, [StringComparison]::OrdinalIgnoreCase) ) }) $bindings += [ordered]@{ webUrl = $webUrl listId = $listId listTitle = $list.Title fieldInternalName = $field.InternalName fieldId = $field.Id.ToString('D') } $rootWeb.AllProperties[$bindingsKey] = (ConvertTo-Json -InputObject @($bindings) -Compress -Depth 5) $rootWeb.Update() # Remove old diagnostic registrations and make the V2 Classic # registrations idempotent. $customActions | Where-Object { $_.Name -eq 'ExpiryIndicator.CommandSet' -or $_.Name -eq 'ExpiryIndicator.Classic.ScriptLink' -or $_.Name -eq 'ExpiryIndicator.Classic.Ribbon' } | ForEach-Object { $_.DeleteObject() } if (-not $SkipClassic) { $webCustomActions | Where-Object { $_.Name -eq 'ExpiryIndicator.Classic.ScriptLink' } | ForEach-Object { $_.DeleteObject() } } $context.ExecuteQuery() if (-not $SkipClassic) { # SharePoint permits Location=ScriptLink only on Web/Site custom # actions, not on List.UserCustomActions. The runtime detects the # current list and its bound expiry field by itself. $scriptAction = $context.Web.UserCustomActions.Add() $scriptAction.Name = 'ExpiryIndicator.Classic.ScriptLink' $scriptAction.Title = 'ExpiryIndicator Classic runtime' $scriptAction.Location = 'ScriptLink' $scriptAction.ScriptSrc = $ClassicScriptUrl $scriptAction.Sequence = 650 $scriptAction.Update() $ribbonLocation = if ($list.BaseType -eq [Microsoft.SharePoint.Client.BaseType]::DocumentLibrary) { 'Ribbon.Documents.Manage.Controls._children' } else { 'Ribbon.ListItem.Actions.Controls._children' } $ribbonXml = @"