diff --git a/clients/birth-biologic/scripts/migrate-datto-to-sharepoint.ps1 b/clients/birth-biologic/scripts/migrate-datto-to-sharepoint.ps1 index c9e9cac..7925ae4 100644 --- a/clients/birth-biologic/scripts/migrate-datto-to-sharepoint.ps1 +++ b/clients/birth-biologic/scripts/migrate-datto-to-sharepoint.ps1 @@ -50,7 +50,8 @@ param( [string]$DattoRoot = "C:\Users\Public\Desktop\Datto Workplace Server Projects", [string]$OnlyFolder = "", [switch]$WhatIf, - [bool]$Resume = $true + [bool]$Resume = $true, + [switch]$DeltaOnly ) $ErrorActionPreference = "Stop" @@ -206,6 +207,10 @@ function Upload-LargeFile { $fileSize = (Get-Item $LocalPath).Length $encoded = Encode-Path $RemotePath + # Delete any existing/partial item first so upload session creation doesn't 409 + $deleteUri = "${GRAPH_ROOT}/sites/${SiteId}/drive/root:/${encoded}" + try { Invoke-Graph -Method DELETE -Uri $deleteUri | Out-Null } catch {} + $sessionUri = "${GRAPH_ROOT}/sites/${SiteId}/drive/root:/${encoded}:/createUploadSession" $sessionBody = '{"item":{"@microsoft.graph.conflictBehavior":"replace"}}' $session = Invoke-Graph -Method POST -Uri $sessionUri -Body $sessionBody @@ -241,6 +246,10 @@ function Upload-LargeFile { } $offset += $read } + } catch { + # Cancel the upload session so outer retries can start a fresh one + try { Invoke-RestMethod -Method DELETE -Uri $uploadUrl -ErrorAction SilentlyContinue } catch {} + throw } finally { $stream.Dispose() Write-Host "" @@ -289,6 +298,24 @@ function Migrate-Folder { $sizeMB = [math]::Round($file.Length / 1MB, 2) Write-Log " [$($done + $skip + $fail + 1)/$total] $relPath ($sizeMB MB)" + if ($DeltaOnly) { + # Check if SharePoint already has this file and it's current + $encoded = Encode-Path $remotePath + $checkUri = "${GRAPH_ROOT}/sites/${SiteId}/drive/root:/${encoded}?select=lastModifiedDateTime,size" + try { + $spItem = Invoke-Graph -Method GET -Uri $checkUri + $spDate = [datetime]$spItem.lastModifiedDateTime + $localDate = $file.LastWriteTimeUtc + if ($spItem.size -gt 0 -and $spDate -ge $localDate) { + $skip++ + continue + } + Write-Log " [DELTA] $relPath (SP: $($spDate.ToString('yyyy-MM-dd')) / Local: $($localDate.ToString('yyyy-MM-dd')))" + } catch { + # Not found in SP — upload it + } + } + if ($WhatIf) { Write-Log " [WHATIF] -> $remotePath" $done++ @@ -325,7 +352,7 @@ function Migrate-Folder { # Main Write-Log "=== BirthBiologic Datto -> SharePoint Migration ===" Write-Log "Source: $DattoRoot" -Write-Log "WhatIf=$WhatIf | Resume=$Resume | OnlyFolder=$(if ($OnlyFolder) { $OnlyFolder } else { '(all)' })" +Write-Log "WhatIf=$WhatIf | Resume=$Resume | DeltaOnly=$DeltaOnly | OnlyFolder=$(if ($OnlyFolder) { $OnlyFolder } else { '(all)' })" if (-not (Test-Path $DattoRoot)) { Write-Log "ERROR: Datto root not found: $DattoRoot" "ERROR" diff --git a/clients/birth-biologic/session-logs/2026-04-21-session.md b/clients/birth-biologic/session-logs/2026-04-21-session.md index e5d7c35..18df5f7 100644 --- a/clients/birth-biologic/session-logs/2026-04-21-session.md +++ b/clients/birth-biologic/session-logs/2026-04-21-session.md @@ -45,6 +45,125 @@ New client onboarded into GuruRMM. Client and site created. Vault entry saved. M - [ ] Install GuruRMM agent on BirthBiologic server via MSI or landing page - [ ] Consent remaining apps in BirthBiologic tenant (user-manager, tenant-admin minimum) -- [ ] Datto Workplace → SharePoint migration: PowerShell script using tenant-admin app-only credentials, reads local Datto file server, uploads to SharePoint via Graph API `Sites.ReadWrite.All` - - BirthBiologic has 14 SharePoint sites (5 new dept sites created 2026-04-20 for Datto migration) - - Datto Workplace server is on-premise at their office (local file system access available once agent is installed) +- [x] Install GuruRMM agent on BB-SERVER — completed, agent online +- [x] Consent tenant-admin app in BirthBiologic tenant for Sites.ReadWrite.All +- [x] Build PowerShell migration script (migrate-datto-to-sharepoint.ps1) +- [x] Supply Management folder — 160/160 files migrated to SharePoint +- [x] Opened Syncro ticket #109277420 for this migration project +- [x] M365 Business Premium license assigned to sysadmin@birthbiologic.com +- [x] SPMT migration launched for Admin, Birth Biologic Activity Reports, Donor Services, Quality Department +- [ ] SPMT migration complete — check morning status +- [ ] After client tests SharePoint access, run delta sync (`-DeltaOnly` flag) for changed files +- [ ] Two duplicate Syncro comments on #109277420 need manual GUI deletion (no API delete for comments) +- [ ] Verify ITSvcs state file entry on BB-SERVER is not causing issues (ITSvcs is ACG-owned, excluded from migration) + +--- + +## Update: 17:58 — Datto-to-SharePoint Migration (Full Detail) + +### What Was Accomplished + +1. **GuruRMM agent installed on BB-SERVER** — agent came online, used as command channel for remote PowerShell execution throughout session. + +2. **Tenant-admin app consented in BirthBiologic tenant** — consent URL used: + `https://login.microsoftonline.com//adminconsent?client_id=709e6eed-0711-4875-9c44-2d3518c47063&redirect_uri=https://azcomputerguru.com` + (redirect URI must match app manifest — `https://azcomputerguru.com`, NOT `https://rmm.azcomputerguru.com`) + +3. **Migration script built** — `D:/claudetools/clients/birth-biologic/scripts/migrate-datto-to-sharepoint.ps1` + - TLS 1.2 enforcement at top (`[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12`) + - Token auto-refresh, resume via JSON state file + - Small files: PUT to `/content`; large files: chunked upload session + - Pre-delete before upload session to avoid 409 conflicts + - `-DeltaOnly` switch: skips files where SP size > 0 and SP lastModified >= local lastWriteTimeUtc + - `-WhatIf` mode, `-OnlyFolder` for per-folder targeting + - All ASCII characters only (no Unicode box-drawing — causes PS5.1 parse errors) + - Hashtable merge via foreach loop (PS5 doesn't support `@{} + @{}`) + - `${encodedPath}` not `$encodedPath:` in URL strings (PS interprets colon as drive reference) + +4. **Supply Management migrated** — 160/160 files via script. + - 159 transferred via RMM-launched script on BB-SERVER + - 1 file (8 MB PDF) timed out RMM channel (~77 KB/s upload); base64-encoded on BB-SERVER, captured stdout, decoded locally, uploaded directly via Python urllib to Graph API + +5. **SPMT launched** for remaining 4 folders: + - Admin → `https://birthbiologic.sharepoint.com/sites/Admin` + - Birth Biologic Activity Reports → `https://birthbiologic.sharepoint.com/sites/Admin` (same site, Documents root; SPMT preserves source folder name as subfolder) + - Donor Services → `https://birthbiologic.sharepoint.com/sites/DonorServices` + - Quality Department → `https://birthbiologic.sharepoint.com/sites/QualityDepartment` + - ITSvcs excluded — that is ACG's folder, not client data + - 20% progress on Donor Services observed before end of session + - Connection noted as slow but making progress + +6. **Syncro ticket #109277420 created** + - Customer: BirthBiologic + - Subject: Datto Workplace to SharePoint Migration + - Contact: Annise + - Assigned: Mike Swanson (user_id 1735) + - Priority: Normal + - Due: 2026-04-22 + - Comment posted with migration status (use `
` line breaks — `