<# Export-GoogleShortcuts.ps1 #> [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$RootPath, [Parameter(Mandatory = $true)][string]$ClientId, [Parameter(Mandatory = $true)][string]$ClientSecret, [string]$TokenCachePath = "$env:LOCALAPPDATA\gdrive-export\token.json", [switch]$Overwrite ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Ensure-Folder([string]$dir) { if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null } } function New-RandomString([int]$len) { $b=New-Object byte[] $len; [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b); ([Convert]::ToBase64String($b)).TrimEnd('=').Replace('+','-').Replace('/','_').Substring(0,$len) } function Base64UrlSha256([string]$s) { $sha=[System.Security.Cryptography.SHA256]::Create(); $h=$sha.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($s)); ([Convert]::ToBase64String($h)).TrimEnd('=').Replace('+','-').Replace('/','_') } function Get-AccessToken { if (Test-Path $TokenCachePath) { try { $cache = Get-Content $TokenCachePath -Raw | ConvertFrom-Json if ($cache.refresh_token) { $r = Invoke-RestMethod -Method Post -Uri "https://oauth2.googleapis.com/token" -Body @{client_id=$ClientId;client_secret=$ClientSecret;refresh_token=$cache.refresh_token;grant_type="refresh_token"} $cache.access_token=$r.access_token; $cache.expires_in=$r.expires_in; $cache | ConvertTo-Json | Set-Content $TokenCachePath return $r.access_token } } catch {} } $port=Get-Random -Min 49152 -Max 65535; $red="http://127.0.0.1:$port/"; $st=New-RandomString 24; $cv=New-RandomString 64; $cc=Base64UrlSha256 $cv $authUrl="https://accounts.google.com/o/oauth2/v2/auth?client_id=$ClientId&redirect_uri=$red&response_type=code&scope=https://www.googleapis.com/auth/drive.readonly&access_type=offline&prompt=consent&state=$st&code_challenge=$cc&code_challenge_method=S256" $l=New-Object System.Net.HttpListener; $l.Prefixes.Add($red); $l.Start(); Start-Process $authUrl | Out-Null Write-Host "Autorisez l'accès dans le navigateur..." -ForegroundColor Yellow $ctx=$l.GetContext(); $b=[System.Text.Encoding]::UTF8.GetBytes("

Authentification OK

"); $ctx.Response.ContentLength64=$b.Length; $ctx.Response.OutputStream.Write($b,0,$b.Length); $ctx.Response.OutputStream.Close(); $l.Stop() $q=$ctx.Request.Url.Query.TrimStart('?'); $code=""; foreach($k in $q-split'&'){if($k-match'code='){$code=$k.Split('=')[1]}} $t=Invoke-RestMethod -Method Post -Uri "https://oauth2.googleapis.com/token" -Body @{client_id=$ClientId;client_secret=$ClientSecret;code=$code;code_verifier=$cv;redirect_uri=$red;grant_type="authorization_code"} $cacheObject = @{refresh_token=$t.refresh_token; access_token=$t.access_token; expires_in=$t.expires_in} Ensure-Folder (Split-Path $TokenCachePath); $cacheObject | ConvertTo-Json | Set-Content $TokenCachePath return $t.access_token } function Search-GoogleFileIdByName([string]$Name, [string]$SearchMime, [string]$Token) { $h = @{ Authorization = "Bearer $Token" } $sn = $Name.Replace("'", "\'") try { $url = "https://www.googleapis.com/drive/v3/files?q=$([uri]::EscapeDataString("name = '$sn' and mimeType = '$SearchMime' and trashed = false"))&fields=files(id,name)" $r = Invoke-RestMethod -Uri $url -Headers $h -Method Get if ($r.files.Count -ge 1) { return $r.files[0].id } } catch {} try { $url = "https://www.googleapis.com/drive/v3/files?q=$([uri]::EscapeDataString("name contains '$sn' and mimeType = '$SearchMime' and trashed = false"))&fields=files(id,name)" $r = Invoke-RestMethod -Uri $url -Headers $h -Method Get if ($r.files.Count -ge 1) { return $r.files[0].id } } catch {} return $null } $rootFull = (Resolve-Path -LiteralPath $RootPath).Path Write-Host "=== GOOGLE DRIVE EXPORT TOOL ===" Write-Host "Source : $rootFull" $extMap = @{ ".gdoc" = @{ exportMime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"; searchMime="application/vnd.google-apps.document"; outExt=".docx"; suffix="_from_gdocs" } ".gsheet" = @{ exportMime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; searchMime="application/vnd.google-apps.spreadsheet"; outExt=".xlsx"; suffix="_from_gsheets" } ".gslides" = @{ exportMime="application/vnd.openxmlformats-officedocument.presentationml.presentation"; searchMime="application/vnd.google-apps.presentation"; outExt=".pptx"; suffix="_from_gslides" } } $targets = Get-ChildItem -LiteralPath $RootPath -Recurse -File | Where-Object { $extMap.ContainsKey($_.Extension.ToLowerInvariant()) } if ($targets.Count -eq 0) { Write-Host "Aucun fichier Google trouvé."; exit } $token = Get-AccessToken foreach ($f in $targets) { $cfg = $extMap[$f.Extension.ToLowerInvariant()] $outName = $f.BaseName + $cfg.suffix + $cfg.outExt $outPath = Join-Path $f.DirectoryName $outName Write-Host "Traitement : $($f.Name)" -NoNewline if ((-not $Overwrite) -and (Test-Path -LiteralPath $outPath)) { Write-Host " -> SKIP (Existe déjà)" -ForegroundColor Gray; continue } $id = Search-GoogleFileIdByName -Name $f.BaseName -SearchMime $cfg.searchMime -Token $token if (-not $id) { Write-Host " -> ECHEC (Introuvable via API)" -ForegroundColor Red; continue } try { $url = "https://www.googleapis.com/drive/v3/files/$id/export?mimeType=$([uri]::EscapeDataString($cfg.exportMime))" Invoke-WebRequest -Uri $url -Headers @{ Authorization = "Bearer $Token" } -OutFile $outPath -ErrorAction Stop Write-Host " -> OK" -ForegroundColor Green } catch { Write-Host " -> ECHEC (Trop volumineux ou Erreur API)" -ForegroundColor Magenta } } Write-Host "`nTerminé."