Session log: BirthBiologic Datto-to-SharePoint migration

Supply Management migrated (160 files), SPMT launched for 4 remaining
folders, Syncro ticket #109277420 opened, SPB license assigned to
sysadmin. Script, errors, SP site map, and next steps documented.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 17:59:37 -07:00
parent 48f1b4b612
commit a9bcbc2580
2 changed files with 151 additions and 5 deletions

View File

@@ -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"

View File

@@ -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/<tenant-id>/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 `<br>` line breaks — `<ul><li>` collapses in Syncro renderer)
7. **M365 Business Premium license assigned to sysadmin@birthbiologic.com**
- SKU: M365 Business Premium (cbdc14ab-d96c-4132-b7f4-1f3a3a819bb4)
- SPB includes EMS — EMS standalone license removed
- sysadmin confirmed as SharePoint admin (needed for SPMT destination access)
### Credentials
- **Tenant-admin app client ID:** `709e6eed-0711-4875-9c44-2d3518c47063`
- **Tenant-admin app secret:** `D:/vault/msp-tools/computerguru-tenant-admin.sops.yaml``credentials.credential`
- **BirthBiologic tenant ID:** Look up via Graph or from previous remediation work
- **GuruRMM JWT secret:** `D:/vault/projects/gururmm/api-server.sops.yaml``credentials.credential`
- **GuruRMM agent API key for BB site:** `grmm_1ZB1qV9Q61b9Noq8BIaZGwLNjZMfF49i`
- **Syncro API key:** `D:/vault/msp-tools/syncro.sops.yaml``credentials.credential`
### SharePoint Site Map (BirthBiologic)
| Datto Folder | SharePoint Site | Site ID |
|---|---|---|
| Admin | birthbiologic.sharepoint.com/sites/Admin | `1baf65c1-...` (see script) |
| Birth Biologic Activity Reports | birthbiologic.sharepoint.com/sites/Admin | same as Admin |
| Donor Services | birthbiologic.sharepoint.com/sites/DonorServices | `bcbfa272-...` (see script) |
| Quality Department | birthbiologic.sharepoint.com/sites/QualityDepartment | `5fd38089-...` (see script) |
| Supply Management | birthbiologic.sharepoint.com/sites/SupplyManagement | `4700ecf3-...` (see script) |
| ITSvcs | EXCLUDED — ACG folder | — |
Full site IDs are hardcoded in the script (`$SITE_MAP` hashtable).
### Infrastructure
- **BB-SERVER:** BirthBiologic on-premise Windows Server 2016, GuruRMM agent installed
- **GuruRMM server:** `https://rmm.azcomputerguru.com` (172.16.3.30:3001)
- **GuruRMM JWT claims required:** `sub`, `role`, `orgs`, `exp`, `iat` — all must be present or 401
- **GuruRMM command body:** must include `command_type: "powershell"` — missing = 422
### Key Errors and Resolutions
| Error | Cause | Fix |
|---|---|---|
| Wrong consent redirect URI 400 | Used rmm.azcomputerguru.com (not in manifest) | Use https://azcomputerguru.com |
| JWT 401 | Missing role/orgs/iat claims | Include all required claims |
| JWT 422 | Missing command_type field | Add `"command_type": "powershell"` |
| Wrong vault path | projects/gururmm/jwt.sops.yaml doesn't exist | Use projects/gururmm/api-server.sops.yaml |
| PS parse error — Unicode | Box-drawing chars in PS5.1 comments | Rewrite all comments ASCII-only |
| PS parse error — hashtable merge | `@{} + @{}` invalid in PS5 | Use foreach loop |
| PS parse error — drive ref | `$encodedPath:/content` | Use `${encodedPath}:/content` |
| TLS error on BB-SERVER | Win Server 2016 defaults TLS 1.0 | Add `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12` |
| 409 Conflict on retry | Partial file left in SP after failed upload | DELETE item before createUploadSession |
| RMM timeout 8 MB file | ~77 KB/s upload > 300s timeout | Base64 on server, capture stdout, upload locally |
| RMSBASIC not assignable | Service plan, not standalone SKU | Use M365 Business Premium (cbdc14ab) |
| SPB 0 seats | License change not saved in admin center | Polled until seat appeared, then assigned |
| Syncro ul/li collapsed | Syncro renderer collapses block-level list tags | Use `<br>` line breaks instead |
### Files Created/Modified
- `D:/claudetools/clients/birth-biologic/scripts/migrate-datto-to-sharepoint.ps1` — full migration script
- `C:/Users/guru/.claude/projects/D--claudetools/memory/feedback_syncro_html.md` — new memory: use `<br>` in Syncro comments
### Next Steps (Morning)
1. Check SPMT migration status — all 4 folders should be complete or near-complete
2. Verify file counts in each SharePoint site match Datto source
3. Notify Annise that migration is complete, ask her to test access
4. After client confirms access, schedule delta sync window: run script with `-DeltaOnly` to catch any files changed since initial migration
5. Delete two duplicate/ugly Syncro comments manually in GUI (ticket #109277420)
6. Update Syncro ticket with completion status and bill time