Skip to content

Commit 79c1eac

Browse files
authored
Merge pull request #886 from Adam-it/refactors-script-spo-cleanup-site-column-usage
Refactors script - 'spo cleanup site column usage'
2 parents a048157 + 6c85173 commit 79c1eac

File tree

4 files changed

+172
-55
lines changed

4 files changed

+172
-55
lines changed

scripts/spo-cleanup-site-column-usage/README.md

Lines changed: 170 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -102,72 +102,190 @@ if (!$reportOnly) {
102102

103103
# [CLI for Microsoft 365](#tab/cli-m365-ps)
104104
```powershell
105+
function Invoke-SiteColumnCleanup {
106+
[CmdletBinding(SupportsShouldProcess)]
107+
param(
108+
[Parameter(Mandatory, HelpMessage = "SharePoint site URL hosting the content types and lists")]
109+
[ValidateNotNullOrEmpty()][string] $SiteUrl,
110+
111+
[Parameter(Mandatory, HelpMessage = "Names of content types that should be scanned for the site column")]
112+
[ValidateNotNullOrEmpty()][string[]] $ContentTypeNames,
113+
114+
[Parameter(Mandatory, HelpMessage = "Display name of the site column to remove")]
115+
[ValidateNotNullOrEmpty()][string] $SiteColumnName,
116+
117+
[Parameter(HelpMessage = "When set, only report usage without removing the column")]
118+
[switch] $ReportOnly
119+
)
120+
121+
begin {
122+
Write-Verbose "Ensuring CLI session is authenticated."
123+
$loginOutput = m365 login --ensure 2>&1
124+
if ($LASTEXITCODE -ne 0) {
125+
throw "Failed to ensure CLI login. CLI output: $loginOutput"
126+
}
105127
106-
# Base variables
107-
$siteURL = "https://tenant.sharepoint.com/sites/sitename"
108-
$contentTypeArray = @('testCT1','CustomContentType1')
109-
$siteColumn = "EffectiveDate"
110-
$reportOnly = $true # If $true, just report. If $false, take action.
111-
112-
$m365Status = m365 status
113-
if ($m365Status -match "Logged Out") {
114-
m365 login
115-
}
116-
117-
# Remove the Site Column from all Content Types which have it
118-
Write-Host -BackgroundColor Blue "Checking Content Types"
119-
120-
foreach ($contentTypeName in $contentTypeArray) {
121-
122-
Write-Host "Checking Content Type $contentTypeName"
123-
124-
$contentType = m365 spo contenttype get --webUrl $siteURL --name $contentTypeName
125-
$contentType = $contentType | ConvertFrom-Json
126-
$schemaXml = $contentType.SchemaXml
127-
$schemaXml = [xml]"<xml>$schemaXml</xml>"
128-
$field = $schemaXml.xml.ContentType.Fields.Field | ? { $_.Name -eq $siteColumn }
129-
130-
if ($field) {
131-
Write-Host -ForegroundColor Green "Found column $($siteColumn) in $($contentTypeName)"
132-
if (!$reportOnly) {
133-
Write-Host -ForegroundColor Yellow "Removing column $($siteColumn) in $($contentTypeName)"
134-
$contentTypeId = $contentType.Id.StringValue
135-
$fieldLinkId = $field.ID.Replace("{", "").Replace("}", "")
136-
m365 spo contenttype field remove --contentTypeId $contentTypeId --fieldLinkId $fieldLinkId --webUrl $siteURL --confirm
128+
$script:Summary = [ordered]@{
129+
ContentTypesChecked = 0
130+
ContentTypesUpdated = 0
131+
ListsChecked = 0
132+
ListsUpdated = 0
133+
SiteColumnRemoved = 0
134+
Failures = 0
137135
}
138136
}
139-
}
140-
141137
142-
# Remove the orphaned Site Column from all lists/libraries which have it
143-
Write-Host -BackgroundColor Blue "Checking Lists"
144-
145-
$lists = m365 spo list list --webUrl $siteURL
146-
$lists = $lists | ConvertFrom-Json
147-
148-
foreach ($list in $lists) {
138+
process {
139+
Write-Host "Checking content types for column '$SiteColumnName'."
140+
141+
foreach ($ctName in $ContentTypeNames) {
142+
$script:Summary.ContentTypesChecked++
143+
Write-Host "Examining content type '$ctName'."
144+
145+
$ctOutput = m365 spo contenttype get --webUrl $SiteUrl --name $ctName --output json 2>&1
146+
if ($LASTEXITCODE -ne 0) {
147+
$script:Summary.Failures++
148+
Write-Warning "Failed to retrieve content type '$ctName'. CLI output: $ctOutput"
149+
continue
150+
}
151+
152+
try {
153+
$ct = $ctOutput | ConvertFrom-Json -ErrorAction Stop
154+
}
155+
catch {
156+
$script:Summary.Failures++
157+
Write-Warning "Unable to parse content type '$ctName'. $($_.Exception.Message)"
158+
continue
159+
}
160+
161+
$query = "[?Title=='$SiteColumnName' || InternalName=='$SiteColumnName']"
162+
$fieldsOutput = m365 spo contenttype field list --webUrl $SiteUrl --contentTypeName $ctName --properties "Title,Id,InternalName" --query $query --output json 2>&1
163+
if ($LASTEXITCODE -ne 0) {
164+
$script:Summary.Failures++
165+
Write-Warning "Failed to list fields for content type '$ctName'. CLI output: $fieldsOutput"
166+
continue
167+
}
168+
169+
try {
170+
$ctFields = $fieldsOutput | ConvertFrom-Json -ErrorAction Stop
171+
}
172+
catch {
173+
$script:Summary.Failures++
174+
Write-Warning "Unable to parse field list for '$ctName'. $($_.Exception.Message)"
175+
continue
176+
}
177+
178+
$fieldLink = $ctFields | Select-Object -First 1
179+
if ($fieldLink) {
180+
Write-Host -ForegroundColor Green "Found field '$SiteColumnName' in content type '$ctName'."
181+
if (-not $ReportOnly -and $PSCmdlet.ShouldProcess("Content type '$ctName'", "Remove field link")) {
182+
$removeOutput = m365 spo contenttype field remove --webUrl $SiteUrl --contentTypeId $ct.Id.StringValue --id $fieldLink.Id --force 2>&1
183+
if ($LASTEXITCODE -ne 0) {
184+
$script:Summary.Failures++
185+
Write-Warning "Failed to remove field '$SiteColumnName' from '$ctName'. CLI output: $removeOutput"
186+
}
187+
else {
188+
$script:Summary.ContentTypesUpdated++
189+
}
190+
}
191+
}
192+
}
149193
150-
$listTitle = $list.Title
151-
Write-Host "Checking list $($listTitle)"
194+
Write-Host "Checking lists for orphaned column '$SiteColumnName'."
195+
$listOutput = m365 spo list list --webUrl $SiteUrl --output json 2>&1
196+
if ($LASTEXITCODE -ne 0) {
197+
$script:Summary.Failures++
198+
throw "Failed to retrieve lists. CLI output: $listOutput"
199+
}
152200
153-
$field = m365 spo field get --webUrl $siteURL --listTitle $listTitle --fieldTitle $siteColumn
201+
try {
202+
$lists = $listOutput | ConvertFrom-Json -ErrorAction Stop
203+
}
204+
catch {
205+
throw "Unable to parse lists response. $($_.Exception.Message)"
206+
}
154207
155-
if ($field) {
156-
Write-Host -ForegroundColor Green "Found column $($siteColumn) in $($listTitle)"
208+
foreach ($list in $lists) {
209+
$script:Summary.ListsChecked++
210+
$listTitle = $list.Title
211+
Write-Host "Examining list '$listTitle'."
212+
213+
$listQuery = "[?Title=='$SiteColumnName' || InternalName=='$SiteColumnName']"
214+
$listFieldsOutput = m365 spo field list --webUrl $SiteUrl --listTitle $listTitle --query $listQuery --output json 2>&1
215+
if ($LASTEXITCODE -ne 0) {
216+
$script:Summary.Failures++
217+
Write-Warning "Failed to list fields for list '$listTitle'. CLI output: $listFieldsOutput"
218+
continue
219+
}
220+
221+
try {
222+
$listFields = $listFieldsOutput | ConvertFrom-Json -ErrorAction Stop
223+
}
224+
catch {
225+
$script:Summary.Failures++
226+
Write-Warning "Unable to parse field list for '$listTitle'. $($_.Exception.Message)"
227+
continue
228+
}
229+
230+
$listField = $listFields | Select-Object -First 1
231+
if (-not $listField) {
232+
continue
233+
}
234+
235+
Write-Host -ForegroundColor Green "Found field '$SiteColumnName' in list '$listTitle'."
236+
if (-not $ReportOnly -and $PSCmdlet.ShouldProcess("List '$listTitle'", "Remove field")) {
237+
$removeFieldOutput = m365 spo field remove --webUrl $SiteUrl --listTitle $listTitle --id $listField.Id --force 2>&1
238+
if ($LASTEXITCODE -ne 0) {
239+
$script:Summary.Failures++
240+
Write-Warning "Failed to remove field from list '$listTitle'. CLI output: $removeFieldOutput"
241+
}
242+
else {
243+
$script:Summary.ListsUpdated++
244+
}
245+
}
246+
}
157247
158-
if (!$reportOnly) {
159-
Write-Host -ForegroundColor Yellow "Removing column $($siteColumn) in $($listTitle)"
160-
m365 spo field remove --webUrl $siteURL --listTitle $listTitle --fieldTitle $siteColumn --confirm
248+
if (-not $ReportOnly -and $PSCmdlet.ShouldProcess("Site '$SiteUrl'", "Remove site column '$SiteColumnName'")) {
249+
$siteFieldOutput = m365 spo field get --webUrl $SiteUrl --title $SiteColumnName --output json 2>&1
250+
if ($LASTEXITCODE -eq 0) {
251+
try {
252+
$siteField = $siteFieldOutput | ConvertFrom-Json -ErrorAction Stop
253+
}
254+
catch {
255+
$script:Summary.Failures++
256+
throw "Unable to parse site column details for '$SiteColumnName'. $($_.Exception.Message)"
257+
}
258+
259+
$removeSiteField = m365 spo field remove --webUrl $SiteUrl --id $siteField.Id --force 2>&1
260+
if ($LASTEXITCODE -ne 0) {
261+
$script:Summary.Failures++
262+
Write-Warning "Failed to remove site column '$SiteColumnName'. CLI output: $removeSiteField"
263+
}
264+
else {
265+
$script:Summary.SiteColumnRemoved++
266+
}
267+
}
268+
else {
269+
Write-Verbose "Site column '$SiteColumnName' was not found at the site level."
270+
}
161271
}
162272
}
163-
}
164273
165-
# Remove the Site Column itself
166-
if (!$reportOnly) {
167-
m365 spo field remove --webUrl $siteURL --fieldTitle $siteColumn --confirm
274+
end {
275+
Write-Host "`nCleanup summary:" -ForegroundColor Cyan
276+
Write-Host " Content types checked : $($script:Summary.ContentTypesChecked)"
277+
Write-Host " Content types updated : $($script:Summary.ContentTypesUpdated)"
278+
Write-Host " Lists checked : $($script:Summary.ListsChecked)"
279+
Write-Host " Lists updated : $($script:Summary.ListsUpdated)"
280+
Write-Host " Site columns removed : $($script:Summary.SiteColumnRemoved)"
281+
Write-Host " Failures : $($script:Summary.Failures)"
282+
}
168283
}
169284
285+
# Example usage:
286+
# Invoke-SiteColumnCleanup -SiteUrl "https://tenant.sharepoint.com/sites/sitename" -ContentTypeNames 'testCT1','CustomContentType1' -SiteColumnName 'EffectiveDate' -ReportOnly
170287
288+
Invoke-SiteColumnCleanup -SiteUrl "https://tenanttocheck.sharepoint.com/sites/PnPDemo2" -ContentTypeNames 'testContentTypeA','testContentTypeB','testContentTypeC' -SiteColumnName 'testColumn1' -ReportOnly
171289
```
172290
[!INCLUDE [More about CLI for Microsoft 365](../../docfx/includes/MORE-CLIM365.md)]
173291
***
-71.9 KB
Loading
-58.4 KB
Loading

scripts/spo-cleanup-site-column-usage/assets/sample.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"Sometimes when we iteratively build out our information architecture, we're over-zealous. It seems like we need a set of Site Columns to maintain metadata on lists or libraries, but in the end, we decide we want to trim away a few of the Site Columns we’ve created."
1010
],
1111
"creationDateTime": "2021-10-15",
12-
"updateDateTime": "2021-10-25",
12+
"updateDateTime": "2025-11-18",
1313
"products": [
1414
"SharePoint"
1515
],
@@ -20,7 +20,7 @@
2020
},
2121
{
2222
"key": "CLI-FOR-MICROSOFT365",
23-
"value": "3.7.0"
23+
"value": "11.0.0"
2424
}
2525
],
2626
"categories": [
@@ -35,7 +35,6 @@
3535
"Get-PnPList",
3636
"Get-PnPField",
3737
"Remove-PnPField",
38-
"m365 status",
3938
"m365 login",
4039
"m365 spo contenttype get",
4140
"m365 spo contenttype field remove",

0 commit comments

Comments
 (0)