Skip to content

Latest commit

 

History

History
460 lines (319 loc) · 12.4 KB

File metadata and controls

460 lines (319 loc) · 12.4 KB

PowerShell → Google Drive Upload: 5 Methoden (API & Desktop)

Ziel: Diese Übersicht soll als GitHub-README dienen. Sie erklärt fünf praktikable Wege, Dateien per PowerShell nach Google Drive zu bringen – inkl. Code und klaren Vor-/Nachteilen.


Überblick

Es gibt zwei große Klassen:

  1. API-basierte Uploads (OAuth / Service Accounts)
  2. Dateisystem-basierte Uploads über Google Drive for desktop (Mirror/Stream)

Die fünf Methoden im Detail:

  • (a) API mit manuellem Benutzer-Login pro Durchlauf
  • (b) Google One / Premium 2 TB + eigener normaler Bot-User
  • (c) Service Accounts (wo es wirklich funktioniert)
  • (d) Drive for Windows Mirror
  • (e) Drive for Windows Stream

Gemeinsame Begriffe (kurz)

  • My Drive: Persönlicher Drive-Bereich eines Google-Users.
  • Shared Drive: Team-/Organisationslaufwerk (typisch in Google Workspace).
  • OAuth Desktop Client: OAuth-Client für lokale Apps/Skripte.
  • Refresh Token: Ermöglicht langfristige, wiederholbare Token-Erneuerung ohne erneuten Login.
  • Service Account: Technische Identität in Google Cloud.

(a) API mit manuellem Benutzer-Login pro Durchlauf

Wann sinnvoll?

  • Proof-of-Concept
  • Sehr seltene, kontrollierte Uploads
  • Kein Token-Persistenz-Wunsch

Ablauf

  1. OAuth Desktop Client in Google Cloud anlegen.
  2. Auth-URL generieren.
  3. Benutzer loggt sich im Browser ein.
  4. Code wird in PowerShell eingefügt.
  5. Access Token wird geholt.
  6. Upload.

PowerShell-Beispiel

# --- Konfiguration ---
$ClientId     = "<CLIENT_ID>"
$ClientSecret = "<CLIENT_SECRET>"
$RedirectUri  = "http://localhost"
$Scope        = "https://www.googleapis.com/auth/drive.file"

# --- 1) Consent-URL generieren (jedes Mal neu!) ---
$authUrl = "https://accounts.google.com/o/oauth2/v2/auth?" + `
           "client_id=$([uri]::EscapeDataString($ClientId))" + `
           "&redirect_uri=$([uri]::EscapeDataString($RedirectUri))" + `
           "&response_type=code" + `
           "&scope=$([uri]::EscapeDataString($Scope))" + `
           "&access_type=online"

Write-Host "Öffne diese URL im Browser:"
Write-Host $authUrl

# --- 2) Code aus Redirect-URL kopieren ---
$AuthCode = Read-Host "Code aus der Browser-URL"

# --- 3) Code → Access Token ---
$token = Invoke-RestMethod -Method Post `
  -Uri "https://oauth2.googleapis.com/token" `
  -ContentType "application/x-www-form-urlencoded" `
  -Body @{
    code          = $AuthCode
    client_id     = $ClientId
    client_secret = $ClientSecret
    redirect_uri  = $RedirectUri
    grant_type    = "authorization_code"
  }

$AccessToken = $token.access_token

# --- 4) Simple Upload ---
$filePath  = "C:\Temp\report.pdf"
$fileBytes = [System.IO.File]::ReadAllBytes($filePath)

Invoke-RestMethod -Method Post `
  -Uri "https://www.googleapis.com/upload/drive/v3/files?uploadType=media" `
  -Headers @{ Authorization = "Bearer $AccessToken" } `
  -ContentType "application/pdf" `
  -Body $fileBytes

Vorteile

  • Einfach verständlich
  • Kein langfristiges Token-Handling
  • Gut für Tests & einmalige Aktionen

Nachteile

  • Nicht automatisierbar ohne Nutzerinteraktion
  • Kein guter Fit für geplante Jobs/Server

(b) Google One / Premium 2 TB + eigener normaler Bot-User

Kernidee

Statt eines GCP-Service-Accounts wird ein zusätzlicher normaler Google-Account erstellt. Dieser Bot-Account wird in die Google One Familienfreigabe aufgenommen, sodass er vom Speicher profitiert.

Danach:

  • Einmalig OAuth-Consent für den Bot-User
  • Refresh Token speichern
  • Wiederholte Uploads ohne erneuten Login

Wann sinnvoll?

  • Privat-/Consumer-Kontext
  • Kein Google Workspace vorhanden
  • Wunsch nach „quasi headless“ Automatisierung

PowerShell-Beispiel (Refresh Token)

$ClientId     = "<CLIENT_ID>"
$ClientSecret = "<CLIENT_SECRET>"
$RefreshToken = "<REFRESH_TOKEN_DES_BOT_USERS>"

function Get-GoogleAccessToken {
  param(
    [Parameter(Mandatory)] [string] $ClientId,
    [Parameter(Mandatory)] [string] $ClientSecret,
    [Parameter(Mandatory)] [string] $RefreshToken
  )

  $resp = Invoke-RestMethod -Method Post `
    -Uri "https://oauth2.googleapis.com/token" `
    -ContentType "application/x-www-form-urlencoded" `
    -Body @{
      client_id     = $ClientId
      client_secret = $ClientSecret
      refresh_token = $RefreshToken
      grant_type    = "refresh_token"
    }

  $resp.access_token
}

function Upload-GDriveMultipart {
  param(
    [Parameter(Mandatory)] [string] $FilePath,
    [Parameter(Mandatory)] [string] $AccessToken,
    [string] $FileName = $(Split-Path $FilePath -Leaf),
    [string] $ParentFolderId
  )

  if (-not (Test-Path $FilePath)) {
    throw "Datei nicht gefunden: $FilePath"
  }

  $metadata = @{ name = $FileName }
  if ($ParentFolderId) { $metadata.parents = @($ParentFolderId) }

  $boundary  = [guid]::NewGuid().ToString()
  $metaJson  = $metadata | ConvertTo-Json -Depth 5
  $fileBytes = [System.IO.File]::ReadAllBytes($FilePath)

  $ms = New-Object System.IO.MemoryStream
  $sw = New-Object System.IO.StreamWriter($ms)

  $sw.WriteLine("--$boundary")
  $sw.WriteLine("Content-Type: application/json; charset=UTF-8")
  $sw.WriteLine()
  $sw.WriteLine($metaJson)

  $sw.WriteLine("--$boundary")
  $sw.WriteLine("Content-Type: application/octet-stream")
  $sw.WriteLine("Content-Transfer-Encoding: binary")
  $sw.WriteLine()
  $sw.Flush()

  $ms.Write($fileBytes, 0, $fileBytes.Length)

  $sw = New-Object System.IO.StreamWriter($ms)
  $sw.WriteLine()
  $sw.WriteLine("--$boundary--")
  $sw.Flush()

  $ms.Position = 0

  Invoke-RestMethod -Method Post `
    -Uri "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart" `
    -Headers @{ Authorization = "Bearer $AccessToken" } `
    -ContentType "multipart/related; boundary=$boundary" `
    -Body $ms.ToArray()
}

$AccessToken = Get-GoogleAccessToken -ClientId $ClientId -ClientSecret $ClientSecret -RefreshToken $RefreshToken

Upload-GDriveMultipart `
  -FilePath "C:\Temp\export.csv" `
  -AccessToken $AccessToken `
  -ParentFolderId "<OPTIONAL_FOLDER_ID>"

Vorteile

  • Sehr praktikabel ohne Workspace
  • Speicher-Upgrade leicht nutzbar
  • Nach Setup recht „automationsfreundlich“

Nachteile

  • Kein echter Service Account
  • Initiales OAuth-Setup nötig
  • Identity-/Governance-Thema bei gemeinsam genutzten Setups

(c) Service Accounts – bei welchen Accounts es geht

Kurzfazit

Service Accounts eignen sich hervorragend für headless Automatisierung – aber realistisch vor allem in Google Workspace-Szenarien.

Funktioniert zuverlässig bei

  1. Google Workspace + Shared Drives

    • Service Account als Mitglied zum Shared Drive hinzufügen.
    • Uploads laufen ohne Benutzer-Login.
  2. Google Workspace + Domain-Wide Delegation (Admin-Setup)

    • Service Account darf im Namen eines Users handeln.

Funktioniert nicht sauber bei

  • Privaten Consumer-Gmail-Konten für direkten, headless Zugriff auf deren My Drive.

PowerShell-Beispiel (JWT → Access Token)

Hinweis: Dieses Beispiel ist am zuverlässigsten mit PowerShell 7+.

function Get-ServiceAccountAccessToken {
  param(
    [Parameter(Mandatory)] [string] $JsonKeyPath,
    [Parameter(Mandatory)] [string] $Scope,
    # Optional für Domain-Wide Delegation:
    [string] $SubjectUser
  )

  $key = Get-Content $JsonKeyPath -Raw | ConvertFrom-Json

  $now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
  $exp = $now + 3600

  $header  = @{ alg = "RS256"; typ = "JWT" } | ConvertTo-Json -Compress
  $payload = @{
    iss   = $key.client_email
    scope = $Scope
    aud   = "https://oauth2.googleapis.com/token"
    iat   = $now
    exp   = $exp
  }
  if ($SubjectUser) { $payload.sub = $SubjectUser }

  $payload = $payload | ConvertTo-Json -Compress

  function To-Base64Url([string]$s) {
    [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($s)).
      TrimEnd("=").Replace("+","-").Replace("/","_")
  }

  $unsigned = "$(To-Base64Url $header).$(To-Base64Url $payload)"

  $rsa = [System.Security.Cryptography.RSA]::Create()
  $rsa.ImportFromPem($key.private_key)

  $sigBytes = $rsa.SignData(
    [Text.Encoding]::UTF8.GetBytes($unsigned),
    [System.Security.Cryptography.HashAlgorithmName]::SHA256,
    [System.Security.Cryptography.RSASignaturePadding]::Pkcs1
  )

  $signature = [Convert]::ToBase64String($sigBytes).
    TrimEnd("=").Replace("+","-").Replace("/","_")

  $jwt = "$unsigned.$signature"

  $resp = Invoke-RestMethod -Method Post `
    -Uri "https://oauth2.googleapis.com/token" `
    -ContentType "application/x-www-form-urlencoded" `
    -Body @{
      grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer"
      assertion  = $jwt
    }

  $resp.access_token
}

$scope = "https://www.googleapis.com/auth/drive"

# SA als sich selbst (Shared Drive Mitgliedschaft vorausgesetzt)
$token = Get-ServiceAccountAccessToken -JsonKeyPath "C:\keys\sa.json" -Scope $scope

# Domain-Wide Delegation (nur Workspace Admin-Szenario)
# $token = Get-ServiceAccountAccessToken -JsonKeyPath "C:\keys\sa.json" -Scope $scope -SubjectUser "user@domain.tld"

Danach Upload wie in (b) über Multipart.

Vorteile

  • Echte headless Automation
  • Ideal für Server, CI/CD, ETL-Jobs
  • Sauber in Enterprise-Governance

Nachteile

  • Für Consumer-My-Drive ungeeignet ohne zusätzliche Konstrukte
  • Admin-/Shared-Drive-Setup nötig
  • Höhere Initialkomplexität

(d) Google Drive for Windows – Mirror

Konzept

Drive wird als lokal gespiegelt eingebunden. PowerShell schreibt einfach in einen Ordner. Der Desktop-Client synchronisiert automatisch.

Wann sinnvoll?

  • Workstation-Automatisierung
  • Schnelle Lösung ohne API
  • Skripte für persönliche Workflows

PowerShell-Beispiel

# Pfad kann je nach Setup variieren
$root = Join-Path $env:USERPROFILE "Google Drive"

$target = Join-Path $root "My Drive\PS_Automation"
New-Item -ItemType Directory -Path $target -Force | Out-Null

"Mirror upload test $(Get-Date)" |
  Set-Content -Path (Join-Path $target "mirror_test.txt") -Encoding UTF8

Copy-Item "C:\Temp\report.pdf" -Destination $target -Force

Vorteile

  • Extrem einfach
  • Keine API-Keys, keine OAuth-Flows im Code
  • Ideal für viele kleine lokale Automations
  • Offline-freundlicher als Stream

Nachteile

  • PC + Drive-App müssen laufen
  • Keine feine API-Kontrolle über Metadaten/Berechtigungen
  • Nicht ideal für „echte Server-Automation“

(e) Google Drive for Windows – Stream

Konzept

Drive erscheint als virtuelles Laufwerk. Dateien werden gestreamt und gecacht.

Wann sinnvoll?

  • Große Drive-Bestände
  • Lokaler Speicher soll geschont werden

PowerShell-Beispiel

# Laufwerksbuchstabe kann abweichen
$driveLetter = "G:"

$target = Join-Path $driveLetter "My Drive\PS_Automation"
New-Item -ItemType Directory -Path $target -Force | Out-Null

"Stream upload test $(Get-Date)" |
  Set-Content -Path (Join-Path $target "stream_test.txt") -Encoding UTF8

Vorteile

  • Spart lokalen Speicher
  • Sehr komfortabel bei großen Datenmengen
  • PowerShell kann wie mit einem normalen Laufwerk arbeiten

Nachteile

  • Stärker netzwerk-/cache-abhängig
  • Bei vielen Dateioperationen potenziell weniger robust als Mirror
  • Ebenfalls nicht headless

Empfehlung nach Use Case

1) Privatnutzer, Automation gewünscht

  • Beste Praxis: (b) Bot-User + Refresh Token
  • Schnellste Lösung: (d) Mirror

2) Unternehmen/IT mit Google Workspace

  • Gold-Standard: (c) Service Account + Shared Drive
  • Optional: Domain-Wide Delegation für User-Impersonation

3) Nur seltene Uploads

  • (a) Manuell pro Durchlauf

Sicherheits- und Betriebsnotizen

  • Refresh Tokens sicher speichern (z. B. Windows Credential Manager).
  • Scopes minimal halten (drive.file statt drive, wenn ausreichend).
  • Für große Dateien ggf. auf „resumable upload“ umsteigen.
  • Bei Desktop-Varianten auf Konflikte achten, wenn mehrere Geräte parallel schreiben.

Minimaler Entscheidungsbaum

  • Muss es headless auf einem Server laufen?

    • Ja(c) (Workspace/Shared Drive)
    • Nein → weiter
  • Willst du API-Kontrolle (Ordner-IDs, Metadaten, elegante Fehlerlogik)?

    • Ja(b) im Consumer-Umfeld, (c) im Workspace
    • Nein(d) oder (e)
  • Skriptstabilität wichtiger als lokaler Speicher?

    • Ja(d) Mirror
    • Nein(e) Stream