Skip to content

Commit b0f44d8

Browse files
authored
Merge pull request #929 from ojopiyo/patch-18
Update README.md
2 parents 39d54e5 + 44d7a4f commit b0f44d8

File tree

2 files changed

+229
-4
lines changed

2 files changed

+229
-4
lines changed

scripts/spo-move-files-library-sites/README.md

Lines changed: 222 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,183 @@
22

33
# Copying files between different SharePoint libraries with custom metadata
44

5-
You might have a requirement to move sample files from a site to a different site, e.g. subset of production files to UAT site to allow testing of solutions. You may want better control over metadata settings, such as ProcessStatus, ensuring files are marked as "Pending" upon transfer . Unlike the default file copy feature, this script enables you to skip the copy process if the destination site lacks a matching folder structure as well setting custom metadata to specific values.
6-
75
## Summary
6+
This script copies files from a source SharePoint Online document library to a destination library while enforcing strict folder‑existence validation and applying controlled metadata values (e.g., setting ProcessStatus to Pending). It is designed for large Microsoft 365 tenants where predictable behaviour, error handling, and operational safety are required. The script prevents accidental writes by skipping transfers when the destination folder structure does not exist.
7+
8+
## Why It Matters
9+
Large enterprises frequently need to migrate or replicate subsets of files between environments such as Production, UAT, and Development. Default copy mechanisms often lack metadata control, overwrite protection, and folder‑validation logic. This script ensures only valid, intentional transfers occur and that files arrive with the correct metadata state for downstream workflows, such as approval processes or automated ingestion pipelines.
10+
11+
## Benefits
12+
- **Operational Safety:** Prevents accidental writes by validating destination folder structure before copying.
13+
- **Metadata Governance:** Ensures consistent metadata values (e.g., ProcessStatus = Pending) during transfer.
14+
- **Tenant‑Scale Reliability:** Uses efficient PnP operations suitable for large libraries and high‑volume tenants.
15+
- **Auditable Execution:** Generates daily log files for compliance and troubleshooting.
16+
- **Environment Segregation:** Supports controlled movement of sample or test files between environments.
17+
18+
# [PnP PowerShell Updated](#tab/pnppsv2)
19+
20+
```PowerShell
21+
param (
22+
[Parameter(Mandatory = $false)]
23+
[string]$SourceSiteUrl = "https://contoso.sharepoint.com/teams/app",
24+
25+
[Parameter(Mandatory = $false)]
26+
[string]$SourceFolderPath = "Shared Documents/Temp Library/test",
27+
28+
[Parameter(Mandatory = $false)]
29+
[string]$DestinationSiteUrl = "https://contoso.sharepoint.com/teams/t-app",
30+
31+
[Parameter(Mandatory = $false)]
32+
[string]$DestinationFolderPath = "Shared Documents/Temp Library/test"
33+
)
34+
35+
# -------------------------
36+
# Logging
37+
# -------------------------
38+
$todayDate = Get-Date -Format "yyyy-MM-dd"
39+
$logFileName = "CopyFilesToSharePoint_$todayDate.log"
40+
$logFilePath = Join-Path -Path $PSScriptRoot -ChildPath $logFileName
41+
42+
function Write-Log {
43+
param (
44+
[string]$Message,
45+
[string]$Color = "White"
46+
)
47+
48+
Write-Host $Message -ForegroundColor $Color
49+
Add-Content -Path $logFilePath -Value "$(Get-Date -Format 'HH:mm:ss') - $Message"
50+
}
51+
52+
Write-Log "==== Script started ====" Cyan
53+
54+
# -------------------------
55+
# Connect to SharePoint
56+
# -------------------------
57+
try {
58+
Connect-PnPOnline -Url $SourceSiteUrl -Interactive
59+
$SourceConn = Get-PnPConnection
60+
Write-Log "Connected to source site" Green
61+
}
62+
catch {
63+
Write-Log "Failed to connect to source site: $($_.Exception.Message)" Red
64+
exit 1
65+
}
66+
67+
try {
68+
Connect-PnPOnline -Url $DestinationSiteUrl -Interactive
69+
$DestConn = Get-PnPConnection
70+
Write-Log "Connected to destination site" Green
71+
}
72+
catch {
73+
Write-Log "Failed to connect to destination site: $($_.Exception.Message)" Red
74+
exit 1
75+
}
76+
77+
# -------------------------
78+
# Validate folders
79+
# -------------------------
80+
function Test-FolderExists {
81+
param (
82+
[string]$FolderPath,
83+
$Connection
84+
)
85+
86+
try {
87+
Get-PnPFolder -FolderSiteRelativeUrl $FolderPath -Connection $Connection -ErrorAction Stop | Out-Null
88+
return $true
89+
}
90+
catch {
91+
return $false
92+
}
93+
}
94+
95+
if (-not (Test-FolderExists -FolderPath $SourceFolderPath -Connection $SourceConn)) {
96+
Write-Log "Source folder does not exist: $SourceFolderPath" Red
97+
exit 1
98+
}
99+
100+
if (-not (Test-FolderExists -FolderPath $DestinationFolderPath -Connection $DestConn)) {
101+
Write-Log "Destination folder does not exist: $DestinationFolderPath" Red
102+
exit 1
103+
}
104+
105+
Write-Log "Source and destination folders validated" Green
106+
107+
# -------------------------
108+
# Copy files
109+
# -------------------------
110+
try {
111+
$sourceFiles = Get-PnPFolderItem `
112+
-FolderSiteRelativeUrl $SourceFolderPath `
113+
-ItemType File `
114+
-Connection $SourceConn `
115+
-ErrorAction Stop
116+
}
117+
catch {
118+
Write-Log "Failed to read source folder: $($_.Exception.Message)" Red
119+
exit 1
120+
}
121+
122+
if ($sourceFiles.Count -eq 0) {
123+
Write-Log "No files found in source folder" Yellow
124+
exit 0
125+
}
126+
127+
foreach ($file in $sourceFiles) {
128+
129+
Write-Log "Processing file: $($file.Name)" Cyan
130+
131+
$stream = $null
132+
133+
try {
134+
# Download
135+
$stream = Get-PnPFile `
136+
-Url $file.ServerRelativeUrl `
137+
-AsMemoryStream `
138+
-Connection $SourceConn `
139+
-ErrorAction Stop
140+
141+
# Upload (overwrite enabled)
142+
$uploaded = Add-PnPFile `
143+
-Folder $DestinationFolderPath `
144+
-FileName $file.Name `
145+
-Stream $stream `
146+
-Overwrite `
147+
-Connection $DestConn `
148+
-ErrorAction Stop
149+
150+
# Try metadata update (non-fatal)
151+
try {
152+
Set-PnPListItem `
153+
-List $uploaded.ListTitle `
154+
-Identity $uploaded.ListItemAllFields.Id `
155+
-Values @{ ProcessStatus = "Pending" } `
156+
-Connection $DestConn `
157+
-ErrorAction Stop
158+
}
159+
catch {
160+
Write-Log "Metadata skipped for $($file.Name) (column may not exist)" Yellow
161+
}
162+
163+
Write-Log "Copied successfully: $($file.Name)" Green
164+
}
165+
catch {
166+
Write-Log "Error copying $($file.Name): $($_.Exception.Message)" Red
167+
}
168+
finally {
169+
if ($stream) {
170+
$stream.Dispose()
171+
}
172+
}
173+
}
174+
175+
Write-Log "==== Script completed ====" Cyan
176+
177+
178+
179+
180+
```
181+
[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)]
8182

9183
# [PnP PowerShell](#tab/pnpps)
10184

@@ -94,12 +268,57 @@ if($_.Name -ne "Forms"){
94268
[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)]
95269
***
96270

271+
## 📄 Sample Script Output
272+
```PowerShell
273+
==== Script started ====
274+
09:14:02 - Connected to source site
275+
09:14:05 - Connected to destination site
276+
09:14:06 - Source and destination folders validated
277+
278+
09:14:07 - Processing file: Report_Q1.pdf
279+
09:14:09 - Copied successfully: Report_Q1.pdf
280+
281+
09:14:10 - Processing file: Budget_2025.xlsx
282+
09:14:12 - Metadata skipped for Budget_2025.xlsx (column may not exist)
283+
09:14:12 - Copied successfully: Budget_2025.xlsx
284+
285+
09:14:13 - Processing file: Notes.txt
286+
09:14:14 - Copied successfully: Notes.txt
287+
288+
==== Script completed ====
289+
```
290+
291+
## 🟡 Sample Output – No Files Found
292+
```PowerShell
293+
==== Script started ====
294+
10:02:11 - Connected to source site
295+
10:02:14 - Connected to destination site
296+
10:02:15 - Source and destination folders validated
297+
10:02:16 - No files found in source folder
298+
299+
==== Script completed ====
300+
```
301+
302+
## 🔴 Sample Output – Failure Case
303+
```PowerShell
304+
==== Script started ====
305+
11:30:44 - Connected to source site
306+
11:30:47 - Connected to destination site
307+
11:30:48 - Source folder does not exist: Shared Documents/Temp Library/test
308+
309+
==== Script completed ====
310+
```
311+
97312
## Contributors
98313

99314
| Author(s) |
100315
|-----------|
101316
| Reshmee Auckloo |
317+
|[Josiah Opiyo](https://github.com/ojopiyo)|
318+
319+
*Built with a focus on automation, governance, least privilege, and clean Microsoft 365 tenants—helping M365 admins gain visibility and reduce operational risk.*
102320

103321

104322
[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]
105-
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-move-files-library-sites" aria-hidden="true" />
323+
324+
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-move-files-library-sites" aria-hidden="true" />

scripts/spo-move-files-library-sites/assets/sample.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"This sample shows how to copy files between different SharePoint libraries with custom metadata with a matching folder structure."
1010
],
1111
"creationDateTime": "2023-10-01",
12-
"updateDateTime": "2023-10-01",
12+
"updateDateTime": "2026-02-08",
1313
"products": [
1414
"SharePoint"
1515
],
@@ -39,6 +39,12 @@
3939
}
4040
],
4141
"authors": [
42+
{
43+
"gitHubAccount": "ojopiyo",
44+
"company": "",
45+
"pictureUrl": "https://github.com/ojopiyo.png",
46+
"name": "Josiah Opiyo"
47+
},
4248
{
4349
"name": "Reshmee Auckloo",
4450
"gitHubAccount": "reshmee011",

0 commit comments

Comments
 (0)