From c4fa4ae57faa81f38e138b5a0d9621d3eca88c7c Mon Sep 17 00:00:00 2001 From: tigeren Date: Mon, 23 Dec 2024 14:04:54 +0800 Subject: [PATCH] v2, use old api endpoint --- Download-ODLivePhotosV2.ps1 | 212 +++++++++++++++++++----------------- 1 file changed, 115 insertions(+), 97 deletions(-) diff --git a/Download-ODLivePhotosV2.ps1 b/Download-ODLivePhotosV2.ps1 index 2c50784..73c15a6 100644 --- a/Download-ODLivePhotosV2.ps1 +++ b/Download-ODLivePhotosV2.ps1 @@ -20,10 +20,19 @@ param ( [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 +# 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 { @@ -32,7 +41,9 @@ function Connect-GraphWithDebug { $scopes = @( "Files.Read.All", "Files.ReadWrite.All", - "offline_access" + "offline_access", + "openid", + "profile" ) Write-Host "Connecting to Microsoft Graph..." @@ -150,7 +161,9 @@ function Get-LivePhotoBundle { [Parameter(Mandatory)] $item, [Parameter(Mandatory)] - $drive + $drive, + [Parameter(Mandatory)] + $AccessToken ) try { @@ -158,88 +171,45 @@ function Get-LivePhotoBundle { 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" - } + # Get the video component using the direct video format API + $videoPath = Join-Path $SaveTo "$baseName.mov" + $tmpFile = Join-Path $SaveTo "tmp-file.mov" - $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" + if (-not (Test-Path $videoPath)) { + Write-Host "Attempting to download video component..." - # Try to get the video component using Graph API - $videoEndpoint = "$itemEndpoint/content" - $videoPath = Join-Path $SaveTo "$baseName.mov" + # 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" - 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 { + # Use exact same headers as original script + $headers = @{ + 'Authorization' = "BEARER $AccessToken" # Note: BEARER in uppercase } - # 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 + 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 } - } - 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 + Rename-Item -Path $tmpFile -NewName $fileName 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 + 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 @@ -250,23 +220,70 @@ function Get-LivePhotoBundle { } } -# Add this helper function to get a Photos-specific token if needed +# Remove the old Get-ODPhotosToken function and use this updated version function Get-ODPhotosToken { param( - [string]$ClientId = "073204aa-c1e0-4e66-a200-e5815a0aa93d" # OneDrive Photos client ID + [string]$ClientId = "073204aa-c1e0-4e66-a200-e5815a0aa93d", + [string]$Scope = "OneDrive.ReadWrite,offline_access,openid,profile", + [string]$RedirectURI = "https://photos.onedrive.com/auth/login" ) - $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 + 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 @@ -279,13 +296,14 @@ if (!(Test-Path $SaveTo)) { New-Item -ItemType Directory -Force -Path $SaveTo } -# Connect to Graph API +# 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 - - # print the items to the console - # $items | Format-List | Out-String | Write-Host if ($items) { # Get drive reference @@ -307,8 +325,8 @@ if (Connect-GraphWithDebug) { 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 + # 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 {