[CmdletBinding()]
Param(
  # https://console.cloud.google.com/iam-admin/serviceaccounts
  [Parameter(Mandatory,Position=0,HelpMessage='Enter path to Google Service Account credentials json file')][string]$serviceCredentialsJsonPath,
  # https://developers.google.com/identity/protocols/oauth2/scopes
  [Parameter(Mandatory,Position=1,HelpMessage='Enter required Google API scopes')][string[]]$scopes,
  [Parameter(Position=3,HelpMessage='Enter token duration in seconds')][int]$duration=3540
)

Set-StrictMode -Version 3.0
$ErrorActionPreference = "Stop"

function base64urlbytes ($Object) {
    ([String]([System.Convert]::ToBase64String($Object))).TrimEnd('=').Replace('+','-').Replace('/','_')
}

function base64url ($Object) {
    base64urlbytes ([System.Text.Encoding]::UTF8.GetBytes($Object))
}

$creds = Get-Content $serviceCredentialsJsonPath | ConvertFrom-Json
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.ImportFromPem($creds.private_key)
$rawheader = [Ordered]@{
    alg = "RS256"
    typ = "JWT"
} | ConvertTo-Json -Compress
$header = base64url $rawheader
[int]$createDate = Get-Date (Get-Date).ToUniversalTime() -UFormat "%s"
$expiryDate = $createDate + $duration
$rawclaims = [Ordered]@{
    iss   = "$($creds.client_email)"
    sub   = "$($creds.client_email)"
    scope = "$($Scopes -join " ")"
    aud   = "https://www.googleapis.com/oauth2/v4/token"
    exp   = "$expiryDate"
    iat   = "$createDate"
} | ConvertTo-Json
$claims = base64url $rawclaims
$sig = base64urlbytes ($rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($header + "." + $claims),"SHA256"))
$jwt = $header + "." + $claims + "." + $sig
$fields = [Ordered]@{
    grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
    assertion  = $jwt
}
$response = Invoke-WebRequest -Uri "https://www.googleapis.com/oauth2/v4/token" -Method Post -Body $fields -ContentType "application/x-www-form-urlencoded"
($response | ConvertFrom-Json).access_token