PowerShellでOAuthしてGmailAPIを叩く
Author: 水卜
Author: 水卜
https://thinkami.hatenablog.com/entry/2016/07/14/063045
こちらはpowershellでOAuth + Gmail APIの実行を行っているすんばらしい記事。
僕もどうあってもpowershellからGmail APIを叩かないといけない状況になったので、恐縮ながら人様のコードに以下の改修を施していきます。
$subject_encoded = ConvertTo-Base64Url "たいとる"
$msg.Subject = "=?utf-8?B?$($subject_encoded)?="
これで日本語も文字化けしない。
=?utf-8?B?<文字>?=
で文字を囲むと、この中の文字はutf-8でbase64デコードするものと解釈してくれる。
こちらは簡単
$cc = New-Object System.Net.Mail.MailAddress $mail["cc"]
$bcc = New-Object System.Net.Mail.MailAddress $mail["bcc"]
$msg.From = $from
$msg.To.Add($to)
$msg.ReplyTo.Add($from)
$msg.Cc.Add($cc)
$msg.Bcc.Add($bcc)
$refresh_body = @{
"refresh_token" = $current_credential.refresh_token;
"client_id" = $auth.client_id;
"client_secret" = $auth.client_secret;
"grant_type" = "refresh_token";
}
try {
$refreshed_credential = Invoke-RestMethod -Method Post -Uri $auth.token_uri -Body $refresh_body
$refreshed_credential | Add-Member refresh_token $refresh_body.refresh_token -Force
}
元記事様ではトークンをリフレッシュした後、戻ってきた認証情報をそのままcredential.jsonとして上書き保存している。
しかしここで戻ってきた認証情報にrefresh_tokenは含まれていない。
なのでもともと持っていたrefresh_tokenをリフレッシュ後の認証情報にAdd-Memberする。
if (!($atts[0] -eq "")) {
foreach($att in $atts){
$file_path = $att
Write-Host $file_path
$file_name = [System.IO.Path]::GetFileName($file_path)
$file_bytes = [System.IO.File]::ReadAllBytes($file_path)
$file_mime = "application/octet-stream"
$attachment = New-Object AE.Net.Mail.Attachment($file_bytes, $file_mime, $file_name)
$attachment.Headers.Add("Content-Disposition", "inline; filename=$($file_name)")
$msg.Attachments.Add($attachment)
}
}
添付ファイルのパスを配列として渡すようにしました。
これも簡単
Param(
[parameter(mandatory=$true)][string]$from,
[parameter(mandatory=$true)][string]$to,
[string]$cc,
[string]$bcc,
[string]$title,
[string]$body,
[array]$atts
)
出来たコードはこちら
gmail_sender.ps1
Param(
[parameter(mandatory=$true)][string]$from,
[parameter(mandatory=$true)][string]$to,
[string]$cc,
[string]$bcc,
[string]$title,
[string]$body,
[array]$atts
)
function ConvertTo-Base64Url($str){
$bytes = [System.Text.Encoding]::UTF8.GetBytes($str)
$b64str = [System.Convert]::ToBase64String($bytes)
$without_plus = $b64str -replace '\+', '-'
$without_slash = $without_plus -replace '/', '_'
$without_equal = $without_slash -replace '=', ''
return $without_equal
}
function Send-Gmail($from, $to, $cc, $bcc, $title, $body, $atts){
$path = Join-Path . "google_credential.psm1"
Import-Module -Name $path
$credential = Get-GoogleCredential
Write-Host $credential
if (-not $credential){
Write-Host "Not Authenticated."
return
}
$dll = Join-Path . "AE.NET.Mail.dll"
Add-Type -Path $dll
$msg = New-Object AE.Net.Mail.MailMessage
if (!([string]::IsNullOrEmpty($cc))) {
$mail_cc = New-Object System.Net.Mail.MailAddress $cc
$msg.Cc.Add($mail_cc)
}
if (!([string]::IsNullOrEmpty($bcc))) {
$mail_bcc = New-Object System.Net.Mail.MailAddress $bcc
$msg.Bcc.Add($mail_bcc)
}
Write-Host $from
$mail_from = New-Object System.Net.Mail.MailAddress $from
$mail_to = New-Object System.Net.Mail.MailAddress $to
$msg.From = $mail_from
$msg.To.Add($mail_to)
Write-Host "Atts length: $($atts.length)"
Write-Host "Atts: $($atts)"
if (!($atts[0] -eq "")) {
foreach($att in $atts){
$file_path = $att
Write-Host $file_path
$file_name = [System.IO.Path]::GetFileName($file_path)
$file_bytes = [System.IO.File]::ReadAllBytes($file_path)
$file_mime = "application/octet-stream"
$attachment = New-Object AE.Net.Mail.Attachment($file_bytes, $file_mime, $file_name)
$attachment.Headers.Add("Content-Disposition", "inline; filename=$($file_name)")
$msg.Attachments.Add($attachment)
}
}
$subject_encoded = ConvertTo-Base64Url $title
$msg.Subject = "=?utf-8?B?$($subject_encoded)?="
$msg.Body = $body
$sw = New-Object System.IO.StringWriter
$msg.Save($sw)
$raw = ConvertTo-Base64Url $sw.ToString()
$body = @{ "raw" = $raw; } | ConvertTo-Json
Write-Host $sw.ToString()
$user_id = "me"
$uri = "https://www.googleapis.com/gmail/v1/users/$($user_id)/messages/send?access_token=$($credential.access_token)"
try {
$result = Invoke-RestMethod $uri -Method POST -ErrorAction Stop -Body $body -ContentType "application/json"
}
catch [System.Exception] {
Write-Host $Error
return
}
Write-Host $result
}
echo "$from, $to, $cc, $bcc, $title, $body, $atts"
$atts_arr = $atts -split ","
Send-Gmail $from $to $cc $bcc $title $body $atts_arr
google_credential.psm1
set CREDENTIAL_FILE (Join-Path . "credential.json")
set SECRET_FILE (Join-Path . "client_id.json")
set DATE_FORMAT "yyyy/MM/dd HH:mm:ss"
set GMAIL_SCOPE "https://www.googleapis.com/auth/gmail.send"
function Save-GoogleCredential($credential){
$credential | Add-Member created_at (Get-Date).ToString($DATE_FORMAT) -Force
$credential_file = Join-Path . $CREDENTIAL_FILE
$credential | ConvertTo-Json | Out-File $CREDENTIAL_FILE -Encoding utf8
}
function Get-GoogleCredential(){
if (-not(Test-Path $SECRET_FILE)) {
Write-Host "Not found client_id.json file"
return $null
}
$json = Get-Content $SECRET_FILE -Encoding UTF8 -Raw | ConvertFrom-Json
$auth = $json.installed
if (Test-Path $CREDENTIAL_FILE) {
$current_credential = Get-Content $CREDENTIAL_FILE -Encoding UTF8 -Raw | ConvertFrom-Json
Write-Host $current_credential.access_token
Write-Host $current_credential.token_type
Write-Host $current_credential.expires_in
Write-Host $current_credential.refresh_token
Write-Host $current_credential.created_at
if (-not ($current_credential.access_token -and $current_credential.token_type -and $current_credential.expires_in `
-and $current_credential.refresh_token -and $current_credential.created_at))
{
Write-Host "No credential file: $($CREDENTIAL_FILE)"
return $null
}
$elapsed_seconds = ((Get-Date) - [DateTime]::ParseExact($current_credential.created_at, $DATE_FORMAT, $null)).TotalSeconds
if ($elapsed_seconds -lt $current_credential.expires_in ) {
Write-Host "Reuse access token..."
return $current_credential
}
else{
Write-Host "Refresh access token..."
$refresh_body = @{
"refresh_token" = $current_credential.refresh_token;
"client_id" = $auth.client_id;
"client_secret" = $auth.client_secret;
"grant_type" = "refresh_token";
}
try {
$refreshed_credential = Invoke-RestMethod -Method Post -Uri $auth.token_uri -Body $refresh_body
$refreshed_credential | Add-Member refresh_token $refresh_body.refresh_token -Force
}
catch [System.Exception] {
Write-Host $Error
return $null
}
Save-GoogleCredential $refreshed_credential
return $refreshed_credential
}
}
Write-Host "New access token..."
$gmail_scope = "https://www.googleapis.com/auth/gmail.send"
$auth_url = "$($auth.auth_uri)?scope=$($GMAIL_SCOPE)"
$auth_url += "&redirect_uri=$($auth.redirect_uris[0])"
$auth_url += "&client_id=$($auth.client_id)"
$auth_url += "&response_type=code&approval_prompt=force&access_type=offline"
Start-Process $auth_url
$code = Read-Host "ブラウザに表示されている認証コードを入力してください。"
try {
$new_body = @{
"client_id" = $auth.client_id;
"client_secret" = $auth.client_secret;
"redirect_uri" = $auth.redirect_uris[0];
"grant_type" = "authorization_code";
"code" = $code;
}
$new_credential = Invoke-RestMethod -Method Post -Uri $auth.token_uri -Body $new_body
}
catch [System.Exception] {
Write-Host $Error
}
Save-GoogleCredential $new_credential
return $new_credential
}
Export-ModuleMember -function Get-GoogleCredential