Init
This commit is contained in:
commit
5109e1ec3b
|
|
@ -0,0 +1,281 @@
|
|||
<#
|
||||
.DESCRIPTION
|
||||
This script attempts to authenticate to OneDrive as client https://photos.onedrive.com
|
||||
(which has permissions to download LivePhotos) and downloads all LivePhotos at a given
|
||||
location within your personal OneDrive.
|
||||
.PARAMETER SaveTo
|
||||
Target path where to save live photos.
|
||||
.PARAMETER PathToScan
|
||||
DOS-Style path on your OneDrive that should be scanned. Most likely '\Pictures\Camera Roll' or any other shared Camera Roll folder.
|
||||
|
||||
.EXAMPLE
|
||||
.\Download-ODLivePhotos.ps1 'C:\Live Photos'
|
||||
|
||||
.NOTES
|
||||
Author: Petr Vyskocil
|
||||
|
||||
There is no error checking, so it is recommended to re-run the command on bigger libraries.
|
||||
If there are any errors during the download (OneDrive sometimes fails randomly with error "Our
|
||||
services aren't available right now. We're working to restore all services as soon as possible.
|
||||
Please check back soon."), next run will download the missing files and skip already downloaded
|
||||
ones.
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string] $SaveTo,
|
||||
[string] $PathToScan = '/Photos/Auto-saved'
|
||||
)
|
||||
|
||||
|
||||
function Get-ODPhotosToken
|
||||
{
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Connect to OneDrive for authentication with a OneDrive web Photos client id. Adapted from https://github.com/MarcelMeurer/PowerShellGallery-OneDrive to mimic OneDrive Photos web OIDC login.
|
||||
Unfortunately using custom ClientId seems impossible - generic OD client IDs are missing the ability to download Live Photos.
|
||||
.PARAMETER ClientId
|
||||
ClientId of OneDrive Photos web app (073204aa-c1e0-4e66-a200-e5815a0aa93d)
|
||||
.PARAMETER Scope
|
||||
Comma-separated string defining the authentication scope (https://dev.onedrive.com/auth/msa_oauth.htm). Default: "OneDrive.ReadWrite,offline_access,openid,profile".
|
||||
.PARAMETER RedirectURI
|
||||
Code authentication requires a correct URI. Must be https://photos.onedrive.com/auth/login.
|
||||
|
||||
.EXAMPLE
|
||||
$access_token=Get-ODPhotosToken
|
||||
Connect to OneDrive for authentication and save the token to $access_token
|
||||
.NOTES
|
||||
Author: Petr Vyskocil
|
||||
#>
|
||||
PARAM(
|
||||
[string]$ClientId = "073204aa-c1e0-4e66-a200-e5815a0aa93d",
|
||||
[string]$Scope = "OneDrive.ReadWrite,offline_access,openid,profile",
|
||||
[string]$RedirectURI ="https://photos.onedrive.com/auth/login",
|
||||
[switch]$DontShowLoginScreen=$false,
|
||||
[switch]$LogOut
|
||||
)
|
||||
$Authentication=""
|
||||
|
||||
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | out-null
|
||||
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") | out-null
|
||||
[Reflection.Assembly]::LoadWithPartialName("System.Web") | out-null
|
||||
if ($Logout)
|
||||
{
|
||||
$URIGetAccessToken="https://login.live.com/logout.srf"
|
||||
}
|
||||
else
|
||||
{
|
||||
$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
|
||||
if ($DontShowLoginScreen)
|
||||
{
|
||||
Write-Output("Logon screen suppressed by flag -DontShowLoginScreen")
|
||||
$form.Opacity = 0.0;
|
||||
}
|
||||
$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)
|
||||
$form.showdialog() | out-null
|
||||
$Authentication = New-Object PSObject
|
||||
# The returned code=XXXX is irrelevant, the actual secrets are sent as cookies:
|
||||
$web.Document.Cookie -split ';' | % {
|
||||
$cookie = $_ -split '='
|
||||
$cookieValue = [uri]::UnescapeDataString($cookie[1])
|
||||
$Authentication | Add-Member NoteProperty $cookie[0].Trim() $cookieValue
|
||||
}
|
||||
|
||||
if (-Not $Authentication.'AccessToken-OneDrive.ReadWrite') {
|
||||
write-error("Cannot get authentication token. This program does not suport token refresh at the moment. Try again, if you fail a few times, restart and try again.")
|
||||
# TODO: Refresh token should be handled here : GET https://photos.onedrive.com/auth/refresh?scope=OneDrive.ReadWrite&refresh_token=
|
||||
# Unfortunately it seems that this page hangs up the WebBrowser control, and we need all the cookies transferred to use...
|
||||
# Deemed not worth debugging for utility that runs for a short period of time
|
||||
return $web
|
||||
|
||||
}
|
||||
|
||||
return $Authentication
|
||||
}
|
||||
|
||||
function Download-LivePhotosAuth
|
||||
{
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Download all Live Photos from a given OneDrive path
|
||||
.PARAMETER AccessToken
|
||||
Access token for OneDrive API that has ability to download live photos.
|
||||
.PARAMETER SaveTo
|
||||
Target path where to save live photos.
|
||||
.PARAMETER PathToScan
|
||||
DOS-Style path on your OneDrive that should be scanned. Most likely '\Pictures\Camera Roll' or any other shared Camera Roll filder you see.
|
||||
.PARAMETER CurrentPath
|
||||
Internal, used for recursion
|
||||
.PARAMETER ElementId
|
||||
Start at folder id (probably only useful internally for recursion too)
|
||||
.PARAMETER Uri
|
||||
Start processing on this URI (again for recursion)
|
||||
|
||||
.EXAMPLE
|
||||
Download-LivePhotosAuth -AccessToken $token -SaveTo 'C:\LivePhotos' -PathToScan '\Pictures\Camera Roll'
|
||||
.NOTES
|
||||
Author: Petr Vyskocil
|
||||
#>
|
||||
PARAM(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$AccessToken,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$SaveTo,
|
||||
[string]$PathToScan='\',
|
||||
[string]$CurrentPath='',
|
||||
[string]$ElementId='',
|
||||
[string]$Uri=''
|
||||
)
|
||||
if (!$PathToScan.EndsWith('\')) { $PathToScan = $PathToScan + '\' }
|
||||
if (!$PathToScan.StartsWith('\')) { $PathToScan = '\' + $PathToScan }
|
||||
if ($Uri -eq '') {
|
||||
if ($ElementId -eq '') {
|
||||
$CurrentPath='\'
|
||||
$Location='root'
|
||||
} else {
|
||||
$Location='items/' + $ElementId
|
||||
}
|
||||
$Uri = 'https://api.onedrive.com/v1.0/drive/' + $Location + '/children?%24filter=photo%2FlivePhoto+ne+null+or+folder+ne+null+or+remoteItem+ne+null&select=fileSystemInfo%2Cphoto%2Cid%2Cname%2Csize%2Cfolder%2CremoteItem'
|
||||
}
|
||||
Write-Output("Calling OneDrive API")
|
||||
Write-Output($Uri)
|
||||
$WebRequest=Invoke-WebRequest -Method 'GET' -Header @{ Authorization = "BEARER "+$AccessToken} -ErrorAction SilentlyContinue -Uri $Uri
|
||||
$Response = ConvertFrom-Json $WebRequest.Content
|
||||
$Response.value | % {
|
||||
$FolderPath = $CurrentPath + $_.name + '\'
|
||||
if ([bool]$_.PSObject.Properties['folder']) {
|
||||
if ($FolderPath.StartsWith($PathToScan) -or $PathToScan.StartsWith($FolderPath)) { # We're traversing the target folder or we're getting into it
|
||||
Write-Output("Checking folder $($_.id) - $($FolderPath)")
|
||||
Download-LivePhotosAuth -AccessToken $AccessToken -SaveTo $SaveTo -PathToScan $PathToScan -CurrentPath $FolderPath -ElementId $_.id
|
||||
}
|
||||
}
|
||||
if ([bool]$_.PSObject.Properties['remoteItem']) {
|
||||
if ($FolderPath.StartsWith($PathToScan) -or $PathToScan.StartsWith($FolderPath)) { # We're traversing the target folder or we're getting into it
|
||||
Write-Output("Checking shared folder $($_.remoteItem.id) - $($FolderPath)")
|
||||
Download-LivePhotosAuth -AccessToken $AccessToken -SaveTo $SaveTo -PathToScan $PathToScan -CurrentPath $FolderPath -ElementId $_.remoteItem.id
|
||||
}
|
||||
}
|
||||
if ([bool]$_.PSObject.Properties['photo']) {
|
||||
if ([bool]$_.photo.PSObject.Properties['livePhoto']) {
|
||||
if ($CurrentPath.StartsWith($PathToScan)) {
|
||||
$TargetPath = $SaveTo + '\' + $CurrentPath.Substring($PathToScan.Length)
|
||||
if ( (Test-Path($TargetPath+$_.name)) -and # Target image exists
|
||||
(Test-Path($TargetPath+([io.fileinfo]$_.name).basename+'.mov')) -and # Target video exists
|
||||
(((Get-Item($TargetPath+$_.name)).Length + (Get-Item($TargetPath+([io.fileinfo]$_.name).basename+'.mov')).Length) -eq $_.size) # size of image and video together is onedrive's size
|
||||
) {
|
||||
Write-Output "Live photo $($_.id) - $($CurrentPath + $_.name) already exists at $($TargetPath) - skipping."
|
||||
} else {
|
||||
Write-Output("Detected live photo $($_.id) - $($CurrentPath + $_.name). Saving image/video pair to $($TargetPath)")
|
||||
Download-SingleLivePhoto -AccessToken $AccessToken -ElementId $_.id -SaveTo $TargetPath -ExpectedSize $_.size -LastModified $_.fileSystemInfo.lastModifiedDateTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ([bool]$Response.PSobject.Properties["@odata.nextLink"])
|
||||
{
|
||||
write-debug("Getting more elements form service (@odata.nextLink is present)")
|
||||
Download-LivePhotosAuth -AccessToken $AccessToken -SaveTo $SaveTo -PathToScan $PathToScan -CurrentPath $CurrentPath -Uri $Response.'@odata.nextLink'
|
||||
}
|
||||
}
|
||||
|
||||
function Download-SingleLivePhoto
|
||||
{
|
||||
<#
|
||||
.DESCRIPTION
|
||||
Download single LivePhoto given it's ElementId and static data.
|
||||
.PARAMETER AccessToken
|
||||
Access token for OneDrive API that has ability to download live photos.
|
||||
.PARAMETER ElementId
|
||||
OneDrive ElementId of a LivePhoto
|
||||
.PARAMETER SaveTo
|
||||
Target path where to save live photos.
|
||||
.PARAMETER ExpectedSize
|
||||
Sum of photo and video file sizes, as reported in the containing folder
|
||||
.PARAMETER LastModified
|
||||
Date to set on a created file.
|
||||
|
||||
.NOTES
|
||||
Author: Petr Vyskocil
|
||||
#>
|
||||
PARAM(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$AccessToken,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$ElementId,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[string]$SaveTo,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[int]$ExpectedSize,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[datetime]$LastModified
|
||||
)
|
||||
|
||||
if (!(Test-Path $SaveTo)) { New-Item -ItemType Directory -Force $SaveTo | Out-Null }
|
||||
|
||||
# video part
|
||||
$Uri = "https://api.onedrive.com/v1.0/drive/items/$($ElementId)/content?format=video"
|
||||
Write-Output("Calling OneDrive API")
|
||||
Write-Output($Uri)
|
||||
$TmpFile = $SaveTo+'tmp-file.mov'
|
||||
$WebRequest=Invoke-WebRequest -Method "GET" -Uri $Uri -Header @{ Authorization = "BEARER "+$AccessToken } -ErrorAction SilentlyContinue -OutFile $TmpFile -PassThru
|
||||
$ActualSize = $WebRequest.RawContentLength
|
||||
$FileName = ($WebRequest.Headers.'Content-Disposition'.Split('=',2)[-1]).Trim('"')
|
||||
if ($FileName) {
|
||||
Write-Debug("Renaming $TmpFile to $FileName")
|
||||
if (Test-Path($SaveTo+$FileName)) { Remove-Item ($SaveTo+$FileName) }
|
||||
Rename-Item -Path $TmpFile -NewName $FileName
|
||||
(Get-Item ($SaveTo+$FileName)).LastWriteTime = $LastModified
|
||||
}
|
||||
|
||||
|
||||
# image part
|
||||
$Uri = "https://api.onedrive.com/v1.0/drive/items/$($ElementId)/content"
|
||||
Write-Output("Calling OneDrive API")
|
||||
Write-Output($Uri)
|
||||
$TmpFile = $SaveTo+'tmp-file.img'
|
||||
$WebRequest=Invoke-WebRequest -Method "GET" -Uri $Uri -Header @{ Authorization = "BEARER "+$AccessToken } -ErrorAction SilentlyContinue -OutFile $TmpFile -PassThru
|
||||
$ActualSize = $ActualSize + $WebRequest.RawContentLength
|
||||
$FileName = ($WebRequest.Headers.'Content-Disposition'.Split('=',2)[-1]).Trim('"')
|
||||
if ($FileName) {
|
||||
Write-Debug("Renaming $TmpFile to $FileName")
|
||||
if (Test-Path($SaveTo+$FileName)) { Remove-Item ($SaveTo+$FileName) }
|
||||
Rename-Item -Path $TmpFile -NewName $FileName
|
||||
(Get-Item ($SaveTo+$FileName)).LastWriteTime = $LastModified
|
||||
}
|
||||
|
||||
if ($ActualSize -ne $ExpectedSize) { Write-Error("Error saving live photo $ElementId. Got $ActualSize bytes, expected $ExpectedSize bytes.") }
|
||||
|
||||
}
|
||||
|
||||
|
||||
Write-Output "Live Photo downloader - Downloads Live Photos from OneDrive camera roll as saved by OneDrive iOS app."
|
||||
Write-Output "(C) 2024 Petr Vyskocil. Licensed under MIT license."
|
||||
Write-Output ""
|
||||
|
||||
|
||||
# This disables powershell progress indicators, speeding up Invoke-WebRequest with big results by a factor of 10 or so
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
Write-Output "Getting OneDrive Authentication token..."
|
||||
$auth=Get-ODPhotosToken
|
||||
|
||||
Write-Output "Downloading Live Photos..."
|
||||
Write-Output $auth
|
||||
Download-LivePhotosAuth -AccessToken $auth.'AccessToken-OneDrive.ReadWrite' -PathToScan $PathToScan -SaveTo $SaveTo
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
<#
|
||||
.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"
|
||||
|
|
@ -0,0 +1 @@
|
|||
.\Download-ODLivePhotos.ps1 -SaveTo 'D:\Photos\OneDrive-Photo\2024\12' -PathToScan '\Photos\Auto-saved\2024\12'
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Example values - you need to replace these with your own from Azure Portal
|
||||
$params = @{
|
||||
SaveTo = "C:\Temp\2024\12"
|
||||
PathToScan = "/Photos/Camera Roll"
|
||||
ClientId = "d581ab07-3a21-44d3-84c4-16b06bef6266" # From Azure App Registration
|
||||
TenantId = "common" # Use "common" for personal Microsoft accounts
|
||||
}
|
||||
|
||||
# '\Photos\Auto-saved\2024\12'
|
||||
# /Photos/Camera Roll
|
||||
.\Download-ODLivePhotosV2.ps1 -SaveTo 'C:\Temp\2024\12' -PathToScan '\Photos\Auto-saved\2024\12' -ClientId 'd581ab07-3a21-44d3-84c4-16b06bef6266' -TenantId 'common'
|
||||
|
||||
Loading…
Reference in New Issue