<# .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 Microsoft.Graph module and install if needed if (-not (Get-Module -ListAvailable -Name Microsoft.Graph)) { Write-Host "Installing Microsoft.Graph module..." Install-Module Microsoft.Graph -Scope CurrentUser -Force } function Connect-GraphWithDebug { try { # Required scopes for OneDrive access $scopes = @( "Files.Read.All", "Files.ReadWrite.All", "offline_access" ) Write-Host "Connecting to Microsoft Graph..." Connect-MgGraph -Scopes $scopes # Get and display current connection info $context = Get-MgContext Write-Host "Connected as: $($context.Account)" Write-Host "Scopes: $($context.Scopes -join ', ')" 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" # Debug output for first few items $items | Select-Object -First 50000 | ForEach-Object { Write-Host "`nItem: $($_.Name)" Write-Host "Type: $($_.File.MimeType)" Write-Host "ID: $($_.Id)" # Additional debug info for photos if ($_.Photo) { Write-Host "Photo metadata found!" $_.Photo | Format-List | Out-String | Write-Host } } 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" 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: $_" return $false } } function Get-LivePhotoBundle { param ( [Parameter(Mandatory)] $item, [Parameter(Mandatory)] $drive ) try { if ($item.File.MimeType -eq "image/heic") { Write-Host "Found HEIC file: $($item.Name)" -ForegroundColor Cyan $baseName = [System.IO.Path]::GetFileNameWithoutExtension($item.Name) # Get current auth context and token $context = Get-MgContext if (-not $context) { throw "No authentication context found" } $token = $context.AccessToken Write-Host "Using token: $($token.Substring(0, 10))..." -ForegroundColor Gray # Get the item details with special fields $itemEndpoint = "https://graph.microsoft.com/v1.0/drives/$($drive.Id)/items/$($item.Id)" Write-Host "Getting item details..." $headers = @{ 'Authorization' = "Bearer $token" 'Accept' = 'application/json' } # First try to get item metadata try { $response = Invoke-MgGraphRequest -Uri $itemEndpoint -Method GET Write-Host "Got item metadata" # Try to get the video component using Graph API $videoEndpoint = "$itemEndpoint/content" $videoPath = Join-Path $SaveTo "$baseName.mov" if (-not (Test-Path $videoPath)) { Write-Host "Attempting to download video component..." # Try different content types $videoHeaders = @{ 'Authorization' = "Bearer $token" 'Accept' = 'video/quicktime' 'Prefer' = 'respond-async' } # Try with special query parameters $queryParams = @( "select=video", "expand=video", "format=mov" ) foreach ($param in $queryParams) { $tryUrl = "${videoEndpoint}?$param" Write-Host "Trying URL: $tryUrl" try { Invoke-MgGraphRequest -Uri $tryUrl -Headers $videoHeaders -Method GET -OutputFilePath $videoPath if ((Test-Path $videoPath) -and (Get-Item $videoPath).Length -gt 0) { Write-Host "Successfully downloaded video component" -ForegroundColor Green return $true } } catch { Write-Host "Attempt failed with $param : $_" -ForegroundColor Yellow Remove-Item $videoPath -ErrorAction SilentlyContinue } } # If all attempts failed, try one last time with direct Graph API call try { $finalEndpoint = "$itemEndpoint/video" Write-Host "Trying final endpoint: $finalEndpoint" Invoke-MgGraphRequest -Uri $finalEndpoint -Method GET -OutputFilePath $videoPath if ((Test-Path $videoPath) -and (Get-Item $videoPath).Length -gt 0) { Write-Host "Successfully downloaded video using final attempt" -ForegroundColor Green return $true } } catch { Write-Host "Final attempt failed: $_" -ForegroundColor Red Remove-Item $videoPath -ErrorAction SilentlyContinue } } else { Write-Host "Video file already exists: $videoPath" -ForegroundColor Yellow return $true } } catch { Write-Host "Failed to get item metadata: $_" -ForegroundColor Red } } return $false } catch { Write-Error "Error processing potential Live Photo: $_" return $false } } # Add this helper function to get a Photos-specific token if needed function Get-ODPhotosToken { param( [string]$ClientId = "073204aa-c1e0-4e66-a200-e5815a0aa93d" # OneDrive Photos client ID ) $scopes = "OneDrive.ReadWrite offline_access" $redirectUri = "https://photos.onedrive.com/auth/login" # Get token using device code flow $deviceCode = Get-MgDeviceCode -ClientId $ClientId -Scopes $scopes Write-Host "Please visit: $($deviceCode.VerificationUri)" Write-Host "Enter code: $($deviceCode.UserCode)" $token = Get-MgToken -DeviceCode $deviceCode -ErrorAction Stop return $token.AccessToken } # 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 } # Connect to Graph API if (Connect-GraphWithDebug) { Write-Host "`nQuerying OneDrive items..." $items = Get-DriveItems -FolderPath $PathToScan # print the items to the console # $items | Format-List | Out-String | Write-Host if ($items) { # Get drive reference $drive = Get-MgDrive -Filter "driveType eq 'personal'" | Select-Object -First 1 if (-not $drive) { Write-Error "Could not get drive reference" 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 $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" } else { Write-Host "No items found to process" } } Write-Host "`nDebug script completed"