sync: auto-sync from GURU-5070 at 2026-06-30 17:21:06

Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-06-30 17:21:06
This commit is contained in:
2026-06-30 17:21:47 -07:00
parent 1b0b313896
commit 01613697c6
9 changed files with 386 additions and 268 deletions

View File

@@ -0,0 +1,12 @@
$ErrorActionPreference = 'SilentlyContinue'
$source = 'C:\Users\Public\Desktop\Datto Workplace Server Projects\Quality Department'
$files = Get-ChildItem -LiteralPath $source -Recurse -File
$big = @($files | Where-Object { $_.Length -ge 4MB })
$bytes = ($files | Measure-Object Length -Sum).Sum
$manifest = $files | ForEach-Object { $_.FullName.Substring($source.Length + 1).Replace('\','/') + '|' + $_.Length }
$manifest | Out-File -FilePath 'C:\Windows\Temp\quality-manifest.txt' -Encoding UTF8
Write-Host "COUNT=$($files.Count)"
Write-Host "BIG4MB=$($big.Count)"
Write-Host "BYTES=$bytes"
Write-Host "MANIFEST_LINES=$($manifest.Count)"
Write-Host "MANIFEST_PATH=C:\Windows\Temp\quality-manifest.txt"

View File

@@ -0,0 +1,101 @@
# Birth Biologic - Quality Systems Department: converge SharePoint to Datto (3768 files)
# Idempotent: skips files already present with matching size. Chunked upload sessions for >=4MB.
# Long-path safe (\\?\). Refreshes Graph token on long runs. Writes progress to a tailable log.
$ErrorActionPreference = 'Stop'
$source = 'C:\Users\Public\Desktop\Datto Workplace Server Projects\Quality Department'
$driveId = 'b!F8BzMb1YakCIWCyWlmczb09LHqtxDxVMpLT6kAwYmsM7NUY4oPLSRq7ng3tJq-E9'
$tenantId = '19a568e8-9e88-413b-9341-cbc224b39145'
$clientId = '709e6eed-0711-4875-9c44-2d3518c47063'
$clientSecret = 'SECRET_PLACEHOLDER'
$logPath = 'C:\Windows\Temp\quality-upload.log'
$manifestPath = 'C:\Windows\Temp\quality-manifest.txt'
"=== upload start ===" | Set-Content -LiteralPath $logPath -Encoding UTF8
function Log($m){ $line = (Get-Date -Format 'HH:mm:ss') + ' ' + $m; Write-Host $line; Add-Content -LiteralPath $logPath -Value $line }
$script:token = $null
$script:tokenAt = $null
function Get-Token {
$body = @{ client_id=$clientId; client_secret=$clientSecret; scope='https://graph.microsoft.com/.default'; grant_type='client_credentials' }
$r = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Body $body
$script:token = $r.access_token
$script:tokenAt = Get-Date
}
function Fresh-Token {
if ($null -eq $script:token -or ((Get-Date) - $script:tokenAt).TotalMinutes -gt 40) { Get-Token }
return $script:token
}
function Enc($rel){ ($rel.Split('/') | ForEach-Object { [uri]::EscapeDataString($_) }) -join '/' }
function Long($p){ if ($p.StartsWith('\\?\')) { $p } else { '\\?\' + $p } }
Get-Token
Log "Token acquired."
$lines = Get-Content -LiteralPath $manifestPath | Where-Object { $_ -and $_.Contains('|') }
$items = foreach($l in $lines){
$idx = $l.LastIndexOf('|')
[pscustomobject]@{ rel = $l.Substring(0,$idx); size = [int64]$l.Substring($idx+1) }
}
# small files first so the visible count climbs fast, then the big ones
$items = $items | Sort-Object size
Log ("Manifest items: " + $items.Count)
# Internal time budget: exit cleanly before any agent-side cap; re-dispatch resumes (idempotent).
$deadline = (Get-Date).AddSeconds(9600)
$timedOut = $false
$uploaded=0; $skipped=0; $errors=0; $bigUp=0; $i=0
foreach($it in $items){
$i++
if ((Get-Date) -gt $deadline){ $timedOut = $true; Log "TIME budget reached at item $i; stopping pass cleanly."; break }
$rel = $it.rel; $size = $it.size; $enc = Enc $rel
$tok = Fresh-Token
$h = @{ Authorization = "Bearer $tok" }
$exists=$false
try {
$meta = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/drives/$driveId/root:/$enc" -Headers $h
if ([int64]$meta.size -eq $size) { $exists=$true }
} catch { $exists=$false }
if ($exists){ $skipped++; if($i % 250 -eq 0){ Log " $i/$($items.Count) up=$uploaded skip=$skipped err=$errors big=$bigUp" }; continue }
$full = Long (Join-Path $source ($rel.Replace('/','\')))
try {
if ($size -lt 4194304){
$bytes = [System.IO.File]::ReadAllBytes($full)
$hh = @{ Authorization = "Bearer $tok"; 'Content-Type'='application/octet-stream' }
Invoke-RestMethod -Method Put -Uri "https://graph.microsoft.com/v1.0/drives/$driveId/root:/$enc`:/content" -Headers $hh -Body $bytes -UseBasicParsing | Out-Null
$uploaded++
} else {
$sessUri = "https://graph.microsoft.com/v1.0/drives/$driveId/root:/$enc`:/createUploadSession"
$sessBody = @{ item = @{ '@microsoft.graph.conflictBehavior'='replace' } } | ConvertTo-Json
$sess = Invoke-RestMethod -Method Post -Uri $sessUri -Headers @{ Authorization="Bearer $tok"; 'Content-Type'='application/json' } -Body $sessBody
$upUrl = $sess.uploadUrl
$fs = [System.IO.File]::Open($full,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::Read)
try {
$chunk = 10485760 # 10 MB (multiple of 320 KiB)
$buf = New-Object byte[] $chunk
$pos = [int64]0
while ($pos -lt $size){
$read = $fs.Read($buf,0,$chunk)
if ($read -le 0){ break }
if ($read -eq $chunk){ $payload = $buf } else { $payload = New-Object byte[] $read; [Array]::Copy($buf,$payload,$read) }
$end = $pos + $read - 1
$ch = @{ 'Content-Range' = "bytes $pos-$end/$size" }
Invoke-RestMethod -Method Put -Uri $upUrl -Headers $ch -Body $payload | Out-Null
$pos += $read
}
} finally { $fs.Close() }
$uploaded++; $bigUp++
Log (" BIG ok " + [math]::Round($size/1MB,1) + "MB: $rel")
}
} catch {
$errors++
Log " ERROR: $rel :: $($_.Exception.Message)"
}
if($i % 100 -eq 0){ Log " $i/$($items.Count) up=$uploaded skip=$skipped err=$errors big=$bigUp" }
}
$allDone = (-not $timedOut)
Log "DONE up=$uploaded skip=$skipped err=$errors big=$bigUp total=$($items.Count) reachedEnd=$allDone"
Write-Host "RESULT up=$uploaded skip=$skipped err=$errors big=$bigUp total=$($items.Count) reachedEnd=$allDone"