Skip to content

Instantly share code, notes, and snippets.

@gitfvb
Last active September 23, 2024 14:46
Show Gist options
  • Select an option

  • Save gitfvb/c60aa0430071a0bbf8126f4b1352185e to your computer and use it in GitHub Desktop.

Select an option

Save gitfvb/c60aa0430071a0bbf8126f4b1352185e to your computer and use it in GitHub Desktop.
Sending Push Notification through PowerShell and Google/Firebase Cloud messaging API

Create a service account like here

grafik

That creates and downloads you a json file (that should not be shared in public)

With this code (mostly created with Co-Pilot) from pwsh/Powershell Core, you can send a message via httpv1:

# This example currently only working in pwsh

$serviceAccountKey = "C:\Users\Florian\Downloads\fcm\fastfire-xxxxx-xxxxxxxxx.json"
$json = Get-Content $serviceAccountKey -Raw | ConvertFrom-Json

# Bereite den privaten Schlüssel vor
$privateKeyPem = $json.private_key
$privateKeyPem = $privateKeyPem -replace "-----BEGIN PRIVATE KEY-----", ""
$privateKeyPem = $privateKeyPem -replace "-----END PRIVATE KEY-----", ""
$privateKeyPem = $privateKeyPem -replace "`n", ""
$privateKeyBytes = [Convert]::FromBase64String($privateKeyPem)

# Lade den privaten Schlüssel
$rsa = [System.Security.Cryptography.RSA]::Create()
$rsa.ImportPkcs8PrivateKey($privateKeyBytes, [ref]0)

# Erstelle das JWT
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$exp = $now + 3600
$header = @{
    alg = "RS256"
    typ = "JWT"
}
$claimSet = @{
    iss = $json.client_email
    scope = "https://www.googleapis.com/auth/cloud-platform"
    aud = "https://oauth2.googleapis.com/token"
    iat = $now
    exp = $exp
}
$headerJson = $header | ConvertTo-Json -Compress
$claimSetJson = $claimSet | ConvertTo-Json -Compress
$headerBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerJson))
$claimSetBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($claimSetJson))
$unsignedToken = "$headerBase64.$claimSetBase64"

# Signiere das JWT
$signature = $rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($unsignedToken), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
$signedToken = "$unsignedToken." + [Convert]::ToBase64String($signature)

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


# Then send the messages
$token = "ccRMv0ul3p_abcdef..."
$projectId = "fastfire-xxxxx"
$apiVersion = "v1"
$base = "https://fcm.googleapis.com"

$params = [Hashtable]@{
    "Uri" = "$( $base )/$( $apiVersion )/projects/$( $projectId )/messages:send"
    "Method" = "POST"
    "ContentType" = "application/json; charset=utf-8"
    "Headers" = [Hashtable]@{
        "Authorization" = "Bearer $( $accessToken )"
    }
    "Body" = (ConvertTo-Json -Compress -InputObject (
        [PSCustomObject]@{
            "message" = [PSCustomObject]@{
                "notification" = [PSCustomObject]@{
                    "title" = "Breaking News"
                    "body" = "New news story available."
                }
                "token"=$token
            }
        }
    ))
} 
 #exit 0
Invoke-RestMethod  @params 

The rsa import does create problems with Windows PowerShell 5.1, so better use PowerShell Core or try to solve it via BouncyCastle, here is an example I got to work

# Installiere BouncyCastle, falls noch nicht geschehen
Install-Package BouncyCastle -Source nuget.org

# Load Bouncy Castle
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\BouncyCastle.1.8.9\lib\BouncyCastle.Crypto.dll"

$serviceAccountKey = "C:\Users\Florian\Downloads\fcm\fastfire-xxxxx-xxxxxxxxx.json"
$json = Get-Content $serviceAccountKey -Raw | ConvertFrom-Json

# Lade den privaten Schlüssel
$sr = [System.IO.StringReader]::new($json.private_key)
$reader = [Org.BouncyCastle.OpenSsl.PemReader]::new($sr)
$keyPair = [Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters]$reader.ReadObject() #[Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair]
$rsaParams = [Org.BouncyCastle.Security.DotNetUtilities]::ToRSAParameters($keyPair)

$rsa = [System.Security.Cryptography.RSA]::Create()
$rsa.ImportParameters($rsaParams)

# Erstelle das JWT
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$exp = $now + 3600
$header = @{
    alg = "RS256"
    typ = "JWT"
}
$claimSet = @{
    iss = $json.client_email
    scope = "https://www.googleapis.com/auth/cloud-platform"
    aud = "https://oauth2.googleapis.com/token"
    iat = $now
    exp = $exp
}
$headerJson = $header | ConvertTo-Json -Compress
$claimSetJson = $claimSet | ConvertTo-Json -Compress
$headerBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerJson))
$claimSetBase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($claimSetJson))
$unsignedToken = "$headerBase64.$claimSetBase64"

# Signiere das JWT
$signature = $rsa.SignData([System.Text.Encoding]::UTF8.GetBytes($unsignedToken), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
$signedToken = "$unsignedToken." + [Convert]::ToBase64String($signature)

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


# Then send the messages
$token = "ccRMv0ul3p_abcdef..."
$projectId = "fastfire-xxxxx"
$apiVersion = "v1"
$base = "https://fcm.googleapis.com"

$params = [Hashtable]@{
    "Uri" = "$( $base )/$( $apiVersion )/projects/$( $projectId )/messages:send"
    "Method" = "POST"
    "ContentType" = "application/json; charset=utf-8"
    "Headers" = [Hashtable]@{
        "Authorization" = "Bearer $( $accessToken )"
    }
    "Body" = (ConvertTo-Json -Compress -InputObject (
        [PSCustomObject]@{
            "message" = [PSCustomObject]@{
                "notification" = [PSCustomObject]@{
                    "title" = "Breaking News"
                    "body" = "New news story available."
                }
                "token"=$token
            }
        }
    ))
}
Invoke-RestMethod  @params 
$params = [Hashtable]@{
"Uri" = "https://fcm.googleapis.com/fcm/send"
"Method" = "POST"
"ContentType" = "application/json"
"Headers" = [Hashtable]@{
"Authorization" = "key=AAAAWqJ3X..."
}
"Body" = (ConvertTo-Json -InputObject (
[PSCustomObject]@{
"data"=[PSCustomObject]@{
"title"="Hello"
"message"="World"
}
"notification" = [PSCustomObject]@{
"title" = "Breaking News"
"body" = "New news story available."
"click_action" = "HANDLE_BREAKING_NEWS"
}
"to"="f-X8-Bsg_UsNl0dxPX2dDN:..."
}))
}
#exit 0
Invoke-RestMethod @params
# For PSCore/Pwsh use the additional flag `-SkipHeaderValidation`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment