heic_powershell/Download-ODLivePhotosV2.ps1

401 lines
13 KiB
PowerShell

<#
.DESCRIPTION
Debug script for accessing OneDrive Live Photos using Microsoft Graph API.
Requires Microsoft.Graph PowerShell module.
.PARAMETER SaveTo
Target path where to save live photos.
.PARAMETER PathToScan
Path on OneDrive to scan (default: '/Photos/Camera Roll')
.EXAMPLE
.\Debug-ODLivePhotos.ps1 -SaveTo 'C:\Live Photos' -PathToScan '/Photos/Camera Roll'
#>
param (
[Parameter(Mandatory)]
[string] $SaveTo,
[string] $PathToScan = '/Photos/Camera Roll',
[string] $ClientId,
[string] $TenantId
)
# Check for and install required Microsoft.Graph modules
$requiredModules = @(
'Microsoft.Graph.Authentication',
'Microsoft.Graph.Files',
'Microsoft.Graph.Users.Actions'
)
foreach ($module in $requiredModules) {
if (-not (Get-Module -ListAvailable -Name $module)) {
Write-Host "Installing $module module..."
Install-Module $module -Scope CurrentUser -Force
}
Import-Module $module -Force
}
function Connect-GraphWithDebug {
try {
$scopes = @(
"Files.Read.All",
"Files.ReadWrite.All",
"Files.Read",
"Files.ReadWrite",
"offline_access",
"openid",
"profile"
)
Write-Host "Connecting to Microsoft Graph..."
# Force a new token by disconnecting first
Disconnect-MgGraph -ErrorAction SilentlyContinue
# Try to connect and verify the connection worked
Connect-MgGraph -Scopes $scopes
$context = Get-MgContext
if (-not $context -or [string]::IsNullOrEmpty($context.Account)) {
Write-Error "Failed to authenticate with Microsoft Graph - no valid context"
return $false
}
Write-Host "Connected as: $($context.Account)"
Write-Host "Scopes: $($context.Scopes -join ', ')"
# Verify we can actually access the API
try {
$testDrive = Get-MgDrive -Filter "driveType eq 'personal'" -ErrorAction Stop | Select-Object -First 1
if (-not $testDrive) {
Write-Error "Could not access OneDrive after authentication"
return $false
}
}
catch {
Write-Error "Failed to verify Graph API access: $_"
return $false
}
return $true
}
catch {
Write-Error "Failed to connect to Microsoft Graph: $_"
return $false
}
}
function Get-DriveItems {
param (
[string]$FolderPath
)
try {
# Verify we're connected
$context = Get-MgContext
if ($null -eq $context) {
throw "Not connected to Microsoft Graph"
}
# Get drive info
$drive = Get-MgDrive -Filter "driveType eq 'personal'" | Select-Object -First 1
Write-Host "Using Drive ID: $($drive.Id)"
# Clean up the path format
$cleanPath = $FolderPath.Replace('\', '/').Trim('/')
Write-Host "Searching in path: /$cleanPath"
try {
# First try to get the folder itself
$folder = Get-MgDriveItem -DriveId $drive.Id -DriveItemId "root:/$cleanPath" -ErrorAction Stop
Write-Host "Found folder: $($folder.Name)"
# Then get its children
$items = Get-MgDriveItemChild -DriveId $drive.Id -DriveItemId $folder.Id
Write-Host "Found $($items.Count) items in specified folder"
return $items
}
catch {
Write-Error "Error accessing path '/$cleanPath': $_"
Write-Host "Full error details:"
$_ | Format-List -Force
return $null
}
}
catch {
Write-Error "Error in Get-DriveItems: $_"
Write-Host "Full error details:"
$_ | Format-List -Force
return $null
}
}
function Test-DownloadItem {
param (
[Parameter(Mandatory)]
[string]$ItemId,
[Parameter(Mandatory)]
[string]$SavePath,
[Parameter(Mandatory)]
$item
)
try {
$fileName = Join-Path $SavePath $item.Name
# Check if file already exists
if (Test-Path $fileName) {
Write-Host "File already exists: $fileName" -ForegroundColor Yellow
return $true
}
Write-Host "Downloading to: $fileName"
# Try to refresh token before download
$context = Get-MgContext
if (-not $context -or $context.ExpiresOn -le (Get-Date)) {
Write-Host "Token expired or missing, reconnecting..." -ForegroundColor Yellow
Connect-GraphWithDebug | Out-Null
}
Get-MgDriveItemContent -DriveId $drive.Id -DriveItemId $ItemId -OutFile $fileName
if (Test-Path $fileName) {
Write-Host "Successfully downloaded: $fileName" -ForegroundColor Green
return $true
}
return $false
}
catch {
Write-Error "Download failed: $_"
Write-Host "Attempting to reconnect and retry..." -ForegroundColor Yellow
try {
Connect-GraphWithDebug | Out-Null
Get-MgDriveItemContent -DriveId $drive.Id -DriveItemId $ItemId -OutFile $fileName
if (Test-Path $fileName) {
Write-Host "Successfully downloaded on retry: $fileName" -ForegroundColor Green
return $true
}
}
catch {
Write-Error "Retry download failed: $_"
}
return $false
}
}
function Get-LivePhotoBundle {
param (
[Parameter(Mandatory)]
$item,
[Parameter(Mandatory)]
$drive
)
try {
if ($item.File.MimeType -eq "image/heic") {
Write-Host "`nDetailed HEIC Analysis:" -ForegroundColor Cyan
Write-Host "------------------------"
Write-Host "File Name: $($item.Name)"
Write-Host "File Size: $($item.Size) bytes"
# Try to download video directly using the same item ID
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($item.Name)
$videoPath = Join-Path $SaveTo "$baseName.mov"
# Use the same video download URL format as the original script
$videoUri = "https://api.onedrive.com/v1.0/drive/items/$($item.Id)/content?format=video"
Write-Host "Attempting direct video download: $videoUri"
try {
$response = Invoke-MgGraphRequest -Method GET -Uri $videoUri -OutputFilePath $videoPath
if ((Test-Path $videoPath) -and (Get-Item $videoPath).Length -gt 0) {
Write-Host "Successfully downloaded video component" -ForegroundColor Green
return $true
} else {
Write-Host "Downloaded file appears to be invalid" -ForegroundColor Yellow
Remove-Item $videoPath -ErrorAction SilentlyContinue
}
}
catch {
Write-Host "Direct video download failed, trying alternative methods..." -ForegroundColor Yellow
Write-Host "Error details:" -ForegroundColor Red
Write-Host "Status Code: $($_.Exception.Response.StatusCode.value__)" -ForegroundColor Red
Write-Host "Status Description: $($_.Exception.Response.StatusDescription)" -ForegroundColor Red
Write-Host "Error Message: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Request URI: $videoUri" -ForegroundColor Red
# Write full error object for debugging
Write-Debug "Full Error Object:"
Write-Debug ($_ | Format-List -Force | Out-String)
}
}
return $false
}
catch {
Write-Error "Error processing potential Live Photo: $_"
return $false
}
}
# Remove the old Get-ODPhotosToken function and use this updated version
function Get-ODPhotosToken {
param(
[string]$ClientId = "073204aa-c1e0-4e66-a200-e5815a0aa93d",
[string]$Scope = "OneDrive.ReadWrite,offline_access,openid,profile",
[string]$RedirectURI = "https://photos.onedrive.com/auth/login"
)
try {
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
$URIGetAccessToken = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=$ClientId&nonce=uv.$(New-Guid).Guid&response_mode=form_post&scope=$Scope&response_type=code&redirect_URI=$RedirectURI"
$form = New-Object Windows.Forms.Form
$form.Text = "Authenticate to OneDrive"
$form.Size = New-Object Drawing.Size @(700,600)
$form.Width = 660
$form.Height = 775
$web = New-Object System.Windows.Forms.WebBrowser
$web.IsWebBrowserContextMenuEnabled = $true
$web.Width = 600
$web.Height = 700
$web.Location = "25, 25"
$web.ScriptErrorsSuppressed = $true
$DocComplete = {
if ($web.Url.AbsoluteUri -match "access_token=|error|code=|logout|/auth/login") {
$form.Close()
}
}
$web.Add_DocumentCompleted($DocComplete)
$form.Controls.Add($web)
$web.Navigate($URIGetAccessToken)
Write-Host "Please login in the opened browser window..."
$form.ShowDialog() | Out-Null
$Authentication = New-Object PSObject
# Get tokens from cookies
$web.Document.Cookie -split ';' | ForEach-Object {
$cookie = $_ -split '='
if ($cookie.Count -eq 2) {
$cookieValue = [uri]::UnescapeDataString($cookie[1])
$Authentication | Add-Member NoteProperty $cookie[0].Trim() $cookieValue
}
}
if (-not $Authentication.'AccessToken-OneDrive.ReadWrite') {
Write-Error "Failed to get authentication token. Please try again."
return $null
}
Write-Host "Successfully obtained authentication token"
return $Authentication
}
catch {
Write-Error "Error in Get-ODPhotosToken: $_"
return $null
}
}
# Main execution
Write-Host "OneDrive Live Photos Debug Script"
Write-Host "Save location: $SaveTo"
Write-Host "Scan path: $PathToScan"
# Create save directory if it doesn't exist
if (!(Test-Path $SaveTo)) {
New-Item -ItemType Directory -Force -Path $SaveTo
}
# Get Graph API token with retry
$maxRetries = 3
$retryCount = 0
$connected = $false
while (-not $connected -and $retryCount -lt $maxRetries) {
$retryCount++
Write-Host "`nAttempting Graph API connection (Attempt $retryCount of $maxRetries)..."
if (Connect-GraphWithDebug) {
$connected = $true
break
}
if ($retryCount -lt $maxRetries) {
Write-Host "Connection failed. Waiting 5 seconds before retry..."
Start-Sleep -Seconds 5
}
}
if (-not $connected) {
Write-Error "Failed to connect to Graph API after $maxRetries attempts. Exiting."
return
}
# Get both authentication tokens
Write-Host "`nGetting OneDrive Photos token..."
$photosToken = Get-ODPhotosToken
if (-not $photosToken) {
Write-Error "Failed to get OneDrive Photos token. Exiting."
return
}
# Get drive reference first
$drive = Get-MgDrive -Filter "driveType eq 'personal'" | Select-Object -First 1
if (-not $drive) {
Write-Error "Could not get drive reference"
return
}
Write-Host "`nQuerying OneDrive items..."
$items = Get-DriveItems -FolderPath $PathToScan
if (-not $items) {
Write-Error "No items found or error occurred while fetching items"
return
}
Write-Host "`nProcessing items for Live Photos..."
foreach ($item in $items) {
Write-Host "`nChecking item: $($item.Name)"
# Process only HEIC files
if ($item.File.MimeType -eq "image/heic") {
Write-Host "Found HEIC file, checking for Live Photo components..."
# Download the HEIC file if it doesn't exist
try {
$result = Test-DownloadItem -ItemId $item.Id -SavePath $SaveTo -item $item
if ($result) {
# Try to get the video component, passing all items and the photos token
$hasVideo = Get-LivePhotoBundle -item $item -drive $drive
if ($hasVideo) {
Write-Host "Successfully processed Live Photo bundle for: $($item.Name)" -ForegroundColor Green
} else {
Write-Host "No Live Photo video component found for: $($item.Name)" -ForegroundColor Yellow
}
}
}
catch {
Write-Host "Error processing $($item.Name): $_" -ForegroundColor Red
}
# Add a small delay between items
Start-Sleep -Milliseconds 500
}
}
Write-Host "`nCompleted processing Live Photos"
Write-Host "`nDebug script completed"