2012/12/03

The Sample Code of Google Apps Email Migration API in PowerShell

Google Apps Email Migration APIの.NET版のサンプルコードmigrationsample.csは、拡張子から判る通り、C#で記述してある。これをコンパイルして実行する方法は、『Google Apps Email Migration APIのC#サンプルプログラムをコンパイルし実行』で紹介した。

しかし、メールデータの移行と言った、一度しか行わないような作業に関して、C#の様なコンパイラ言語を使うのは牛刀割鶏、大げさすぎる。そこでこの記事では、Windowsの新スクリプト言語であるPowerShellを使う方法を紹介する。

コードmigrationsample.ps1(ダウンロード)は以下の通り。
<#
.SYNOPSIS
Sample script to demonstrate the use of the Google Apps Domain Migration API client library.

.DESCRIPTION
Sample script to demonstrate the use of the Google Apps Domain Migration API client library.
The original C# version is from https://developers.google.com/google-apps/email-migration/.

.INPUTS
Nothing.

.OUTPUTS
Nothing.
#>

[CmdletBinding()param(
    # The hosted domain (e.g. example.com) in which the migration will occur.
    [String]
    [parameter(Mandatory=$true)]
    $domain,

    # The username of the administrator or user migrating mail.
    [String]
    [parameter(Mandatory=$true)]
    $adminUsername,

    # The password of the administrator or user migrating mail.
    [String]
    [parameter(Mandatory=$true)]
    $adminPassword,

    # The username to which emails should be migrated.
    # End users can only transfer mail to their own mailboxes.
    # If unspecified, will default to login_email.
    [String]
    [parameter(Mandatory=$true)]
    $destinationUser = ''
)

#####
$REDIST_PATH = "$Env:ProgramFiles\Google\Google Data API SDK\Redist"
Add-Type -Path "$REDIST_PATH\Google.GData.Apps.dll"
Add-Type -Path "$REDIST_PATH\Google.GData.Client.dll"
Add-Type -Path "$REDIST_PATH\Google.GData.Extensions.dll"

$LabelFriend = New-Object Google.GData.Extensions.Apps.LabelElement("Friends")
$LabelEventInvitations = New-Object Google.GData.Extensions.Apps.LabelElement("Event Invitations")

$PropertyElementINBOX = [Google.GData.Extensions.Apps.MailItemPropertyElement]::INBOX
$PropertyElementSTARRED = [Google.GData.Extensions.Apps.MailItemPropertyElement]::STARRED
$PropertyElementUNREAD = [Google.GData.Extensions.Apps.MailItemPropertyElement]::UNREAD


[String]$rfcTxt = @"
Received: by 10.143.160.15 with HTTP; Mon, 16 Jul 2007 10:12:26 -0700 (PDT)
Message-ID: <_message_id_ mail.gmail.com="mail.gmail.com">
Date: Mon, 16 Jul 2007 10:12:26 -0700
From: "Mr. Serious" 
To: "Mr. Admin" 
Subject: Random Subject
MIME-Version: 1.0
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
Delivered-To: testadmin@apps-provisioning-test.com

This is a message delivered via DMAPI
"@

$random = New-Object Random


if($destinationUser -eq '') {
    $destinationUser = $adminUsername
}


$mailItemService = New-Object Google.GData.Apps.Migration.MailItemService($domain, "Sample Migration Application")
$mailItemService.setUserCredentials("$adminUsername@$domain", $adminPassword)


<#
.DESCRIPTION
Generates a random RFC822 message based on the template in rfcTxt.
We have to randomly modify the subject and message ID to prevent duplicate
supression by the Gmail server.

.INPUTS
Nothing.

.OUTPUTS
The randomly-modified RFC822 message.
#>
Function GenerateRandomRfcText
{
    ($rfcTxt -replace "Random Subject", "Random Subject $($random.Next())") `
        -replace "_message_id_", "$($random.Next())"
}


<#
.DESCRIPTION
Helper method to set up a new MailItemEntry.

.INPUTS
Nothing.

.OUTPUTS
The newly created MailItemEntry.
#>
Function SetupMailItemEntry
{
    param(
        # The batch ID for this entry.
        [String]$batchId
    )

    $entry = New-Object Google.GData.Apps.Migration.MailItemEntry

    [void]$entry.Labels.Add($LabelFriend)
    [void]$entry.Labels.Add($LabelEventInvitations)

    [void]$entry.MailItemProperties.Add($PropertyElementINBOX)
    [void]$entry.MailItemProperties.Add($PropertyElementSTARRED)
    [void]$entry.MailItemProperties.Add($PropertyElementUNREAD)

    $entry.Rfc822Msg = New-Object Google.GData.Extensions.Apps.Rfc822MsgElement(GenerateRandomRfcText)

    $entry.BatchData = New-Object Google.GData.Client.GDataBatchEntryData
    $entry.BatchData.Id = $batchId

    $entry
}


<#
.DESCRIPTION
Demonstrates inserting several mail items in a batch.

.INPUTS
Nothing.

.OUTPUTS
A MailItemFeed with the results of the insertions.
#>
Function BatchInsertMailItems
{
    param(
        # The number of entries to insert.
        [Int]$numToInsert
    )

    [Google.GData.Apps.Migration.MailItemEntry[]]$entries = @()

    # Set up the mail item entries to insert.
    foreach($index in 0..($numToInsert-1)) {
        $entries += SetupMailItemEntry([String]$index)
    }

    # Execute the batch request and print the results.
    [Google.GData.Apps.Migration.MailItemFeed]$batchResult = $mailItemService.Batch($domain, $destinationUser, $entries)
    [Google.GData.Client.AtomEntry]$entry = $Null
    foreach($entry in $batchResult.Entries) {
        [Google.GData.Client.GDataBatchEntryData]$batchData = $entry.BatchData

        Write-Host "Mail message $($batchData.Id): $($batchData.Status.Code) $($batchData.Status.Reason)"
    }

    $batchResult
}

try {
    # Insert several emails in a batch.
    [Google.GData.Apps.Migration.MailItemFeed]$batchResults = BatchInsertMailItems(10)
} catch [Google.GData.Client.GDataRequestException] {
    Write-Host ("Operation failed ({0}): {1}" -f $Error[0].Message, $Error[0].ResponseString)
}
このコードを実行するには、Google Data APIをインストールしておく必要がある。『Google Data API Installer MSIのダウンロード』および『Google Data API Installer MSIのインストール』の通りにインストールする。
また、『PowerShell実行ポリシの変更』の通り、ローカルスクリプトを実行できるようPowerShellの実行ポリシを変更しておく必要もある。

以下の通り実行する。
PS C:\Users\user01> .\migrationsample.ps1 example.co.jp appsmaster 'password' appsuser
Mail message : 201 Created
Mail message : 201 Created
Mail message : 201 Created
Mail message : 201 Created
Mail message : 201 Created
Mail message : 201 Created
Mail message : 201 Created
Mail message : 201 Created
Mail message : 201 Created
Mail message : 201 Created
PS C:\Users\user01>
この例では、10通すべてのメールデータの処理結果が状態コード201で、Google Apps上に移行されたことが判る。

なお、実際のメールデータを移行した場合、以下のような結果を得る。
状態コード201 / Created
正常終了。Google Apps上にメールデータが作成された。ただし、このメッセージを受けてから、Gmailのウェブインターフェース上から参照できるようになるまでには、若干(数分間~数十分間)時間がかかることがある。
状態コード400 / Permanent failure: BadAttachment
添付ファイル形式の異常。Gmailでは、実行形式の添付ファイルが禁止されているが、その様なメールを移行しようとした場合。
状態コード400 / Permanent failure: Insert failed, badly formed message.
メールのサイズが大きすぎる。最大一通当り10MB。
状態コード400 / Bad Request
RFC5322形式(所謂RFC822形式)として正しくないデータ。ドキュメントによると、From:To:、およびDate:のヘッダフィールドが必須。未検証。
状態コード503 / The server is currently busy and could not complete your request. Please try again in 30 seconds.
クラウド側が高負荷状態のため、処理に失敗した。ドキュメントによると、この状態コードを得た場合、xponential backoff方式で再試行せよ、とある。例えば、1回この状態なら次の再試行は30秒後、続けて2回この状態なら次の再試行は60秒後、…と言う様に、続けてn回この状態なら次の再試行は30×2^(n-1)秒後、と言う様に処理する。
実行時エラー
以下の様な実行時エラーが発生する場合がある。バッチ呼び出しの最大サイズ制限に抵触している場合などに発生する。一回のバッチ呼び出しで、メールサイズの合計は、25MB以下に抑えなければならない。
"3" 個の引数を指定して "Batch" を呼び出し中に例外が発生しました: "転送接続にデータを書き込めません: 既存の接続はリモート ホストに強制的に切断されました。。"
発生場所 C:\path\to\script.ps1:line number 文字:charactor number
+                 [Google.GData.Apps.Migration.MailItemFeed]$result = $script:mailItemService.Batch <<<< ($domain, $user, $list)
    + CategoryInfo          : NotSpecified: (:) []、MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException
"3" 個の引数を指定して "Batch" を呼び出し中に例外が発生しました: "Execution of request failed: https://apps-apis.google.com/a/feeds/migration/2.0/example.com/username/mail/batch"
発生場所 C:\path\to\script.ps1:line number 文字:charactor number
+                 [Google.GData.Apps.Migration.MailItemFeed]$result = $script:mailItemService.Batch <<<< ($domain, $user, $list)
    + CategoryInfo          : NotSpecified: (:) []、MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

コーディング上注意して欲しいのは、メールデータはUTF-8でなければならない点。日本語のメールデータは通常、ISO-2022-JPで符号化されているため、そのままでは不正なデータとされ、処理されない。これを回避するためには、メールデータ全体をBASE64で符号化する必要がある。

0 件のコメント: