heic_powershell/Download-ODLivePhotosV2.ps1

352 lines
12 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 {
# Required scopes for OneDrive access
$scopes = @(
"Files.Read.All",
"Files.ReadWrite.All",
"offline_access",
"openid",
"profile"
)
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,
[Parameter(Mandatory)]
$AccessToken
)
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 the video component using the direct video format API
$videoPath = Join-Path $SaveTo "$baseName.mov"
$tmpFile = Join-Path $SaveTo "tmp-file.mov"
if (-not (Test-Path $videoPath)) {
Write-Host "Attempting to download video component..."
# Use exactly the same endpoint and headers as original script
$videoUri = "https://api.onedrive.com/v1.0/drive/items/$($item.Id)/content?format=video"
Write-Host "Using video endpoint: $videoUri"
try {
# Use exact same headers as original script
$headers = @{
'Authorization' = "BEARER $AccessToken" # Note: BEARER in uppercase
}
Write-Host "Calling OneDrive API for video..."
$WebRequest = Invoke-WebRequest -Method "GET" -Uri $videoUri -Header $headers -ErrorAction SilentlyContinue -OutFile $tmpFile -PassThru
Write-Host "WebRequest: Done Calling OneDrive API"
if ((Test-Path $tmpFile) -and (Get-Item $tmpFile).Length -gt 0) {
$fileName = ($WebRequest.Headers.'Content-Disposition'.Split('=',2)[-1]).Trim('"')
if ($fileName) {
Write-Host "Renaming $tmpFile to $fileName"
if (Test-Path $videoPath) {
Remove-Item $videoPath
}
Rename-Item -Path $tmpFile -NewName $fileName
return $true
}
}
}
catch {
Write-Host "Failed to download video component: $_" -ForegroundColor Red
Remove-Item $tmpFile -ErrorAction SilentlyContinue
}
} else {
Write-Host "Video file already exists: $videoPath" -ForegroundColor Yellow
return $true
}
}
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 both authentication tokens
Write-Host "Getting Graph API token..."
if (Connect-GraphWithDebug) {
Write-Host "`nGetting OneDrive Photos token..."
$photosToken = Get-ODPhotosToken
Write-Host "`nQuerying OneDrive items..."
$items = Get-DriveItems -FolderPath $PathToScan
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, passing all items and the photos token
$hasVideo = Get-LivePhotoBundle -item $item -drive $drive -AccessToken $photosToken.'AccessToken-OneDrive.ReadWrite'
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"