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:
@@ -50,7 +50,8 @@ param(
|
|||||||
[string]$DattoRoot = "C:\Users\Public\Desktop\Datto Workplace Server Projects",
|
[string]$DattoRoot = "C:\Users\Public\Desktop\Datto Workplace Server Projects",
|
||||||
[string]$OnlyFolder = "",
|
[string]$OnlyFolder = "",
|
||||||
[switch]$WhatIf,
|
[switch]$WhatIf,
|
||||||
[bool]$Resume = $true
|
[bool]$Resume = $true,
|
||||||
|
[switch]$DeltaOnly
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@@ -206,6 +207,10 @@ function Upload-LargeFile {
|
|||||||
$fileSize = (Get-Item $LocalPath).Length
|
$fileSize = (Get-Item $LocalPath).Length
|
||||||
$encoded = Encode-Path $RemotePath
|
$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"
|
$sessionUri = "${GRAPH_ROOT}/sites/${SiteId}/drive/root:/${encoded}:/createUploadSession"
|
||||||
$sessionBody = '{"item":{"@microsoft.graph.conflictBehavior":"replace"}}'
|
$sessionBody = '{"item":{"@microsoft.graph.conflictBehavior":"replace"}}'
|
||||||
$session = Invoke-Graph -Method POST -Uri $sessionUri -Body $sessionBody
|
$session = Invoke-Graph -Method POST -Uri $sessionUri -Body $sessionBody
|
||||||
@@ -241,6 +246,10 @@ function Upload-LargeFile {
|
|||||||
}
|
}
|
||||||
$offset += $read
|
$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 {
|
} finally {
|
||||||
$stream.Dispose()
|
$stream.Dispose()
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
@@ -289,6 +298,24 @@ function Migrate-Folder {
|
|||||||
$sizeMB = [math]::Round($file.Length / 1MB, 2)
|
$sizeMB = [math]::Round($file.Length / 1MB, 2)
|
||||||
Write-Log " [$($done + $skip + $fail + 1)/$total] $relPath ($sizeMB MB)"
|
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) {
|
if ($WhatIf) {
|
||||||
Write-Log " [WHATIF] -> $remotePath"
|
Write-Log " [WHATIF] -> $remotePath"
|
||||||
$done++
|
$done++
|
||||||
@@ -325,7 +352,7 @@ function Migrate-Folder {
|
|||||||
# Main
|
# Main
|
||||||
Write-Log "=== BirthBiologic Datto -> SharePoint Migration ==="
|
Write-Log "=== BirthBiologic Datto -> SharePoint Migration ==="
|
||||||
Write-Log "Source: $DattoRoot"
|
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)) {
|
if (-not (Test-Path $DattoRoot)) {
|
||||||
Write-Log "ERROR: Datto root not found: $DattoRoot" "ERROR"
|
Write-Log "ERROR: Datto root not found: $DattoRoot" "ERROR"
|
||||||
|
|||||||
@@ -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
|
- [ ] Install GuruRMM agent on BirthBiologic server via MSI or landing page
|
||||||
- [ ] Consent remaining apps in BirthBiologic tenant (user-manager, tenant-admin minimum)
|
- [ ] 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`
|
- [x] Install GuruRMM agent on BB-SERVER — completed, agent online
|
||||||
- BirthBiologic has 14 SharePoint sites (5 new dept sites created 2026-04-20 for Datto migration)
|
- [x] Consent tenant-admin app in BirthBiologic tenant for Sites.ReadWrite.All
|
||||||
- Datto Workplace server is on-premise at their office (local file system access available once agent is installed)
|
- [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
|
||||||
|
|||||||
Reference in New Issue
Block a user