352 lines
12 KiB
PowerShell
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" |