メインコンテンツへスキップ
見出し画像
Photo byakisuke0925

【第578回】 Marketing Cloud Next : 複数画像を一括でアップロード公開する

    Nobuyuki Watanabe

    Marketing Cloud Next Growth & Advanced Editions の CMS Workspace では、画像などのコンテンツを 1 件ずつアップロードできます。

    数件程度であれば手作業でも特に問題ありませんが、例えば 100 個の画像を登録して公開するとなると、1 件ずつアップロードしていくのは大変です。

    Salesforce CMS には、複数のコンテンツを ZIP ファイルからまとめて登録できる標準の Import content 機能があります。

    そこで今回は、この機能を利用して、Marketing Cloud Engagement の SFTP に配置した複数の画像から、Marketing Cloud Next の CMS へ一括インポートできる ZIP ファイルを自動生成する方法 を解説します。


    ZIP ファイル生成をブラウザ上で処理可能なツールの紹介

    ZIP ファイルの生成については、私がこの記事を公開した後、ポーランドの Marketing Champion である Szymon Lewandowski(シモン・レヴァンドフスキ)さんと、メキシコの Salesforce MVP である Rodrigo Santander(ロドリゴ・サンタンデール)さんが、それぞれブラウザ上で ZIP ファイルを生成できる素晴らしいツールを作成してくれました。

    どちらも手軽に利用できますので、実際に試してみて、使いやすいと感じた方のツールをご利用ください。

    画像
    https://www.linkedin.com/in/szymon-lewandowski/
    画像
    https://www.linkedin.com/in/rodrigo-santander/

    このツールがあれば、私が説明した SFTP も PowerShell も不要です。ZIP 作成後は、この記事の「ZIP が完成」セクションに進んでください。


    今回の構成

    今回の処理は、以下のようなものにしてあります。

    Marketing Cloud Engagement の SFTP
    ↓
    WinSCP
    ↓
    画像 + manifest.csv をダウンロード
    ↓
    PowerShell
    ↓
    content.json を自動生成
    ↓
    CMS Import 形式にフォルダを変換
    ↓
    ZIP を自動生成
    ↓
    Marketing Cloud Next CMS Workspace
    ↓
    Import content

    ちなみに、画像の保管場所は SFTP でなくとも、Amazon S3、Azure Blob Storage、Google Cloud Storage、独自 SFTP、ローカルフォルダ(自分の PC 上のフォルダ)などを利用し、ZIP の生成部分だけを共通化できます。

    今回は、私が画像の保管場所として Marketing Cloud Engagement の SFTP を利用できるため、SFTP を使用しています。SFTP に配置した場合、ファイルは 21 日で削除処理対象になりますので注意が必要です。


    サンプル画像の準備

    まず、今回のサンプル用として 10 個の画像を用意しました。

    summer_hero.png
    autumn_sale.png
    winter_campaign.png
    spring_collection.png
    product_feature.png
    new_arrival.png
    member_offer.png
    event_banner.png
    brand_story.png
    company_logo.png

    ただし、画像ファイルだけでは CMS へ登録する際のタイトルや Alt Text が分かりません。そこで、画像と一緒に管理用 CSV を配置します。

    今回は、manifest.csv という名前にします。

    fileName,title,altText,folderPath
    summer_hero.png,Summer Hero,Summer campaign hero image,Practice 10 Images
    autumn_sale.png,Autumn Sale,Autumn sale promotional image,Practice 10 Images
    winter_campaign.png,Winter Campaign,Winter campaign banner image,Practice 10 Images
    spring_collection.png,Spring Collection,Spring collection promotional image,Practice 10 Images
    product_feature.png,Product Feature,Featured product promotional image,Practice 10 Images
    new_arrival.png,New Arrival,New arrival announcement image,Practice 10 Images
    member_offer.png,Member Offer,Special offer image for members,Practice 10 Images
    event_banner.png,Event Banner,Event announcement banner image,Practice 10 Images
    brand_story.png,Brand Story,Brand story visual image,Practice 10 Images
    company_logo.png,Company Logo,Company logo image,Practice 10 Images
    画像

    つまり、以下を SFTP にセットで配置する形になります。

    画像ファイル
    +
    登録情報(manifest.csv)

    なお、今回の PowerShell では、上記 manifest.csv の fileName と、SFTP に配置された「画像ファイル名」をマッチングして処理しています。

    そのため、CSV に記載するファイル名は、実際の画像ファイル名と一致させる必要があります。ファイル名が一致しない場合、処理対象からスキップされて ZIP には含まれず、PowerShell 実行時に警告が表示されます。


    CMS Workspace に格納用のフォルダを作成

    今回、CMS Workspace の特定のフォルダに格納したいと思うので、事前に「Practice 10 Images」というフォルダを作成しました。

    画像

    manifest.csv では folderPath 列の値と完全一致させる必要があります。

    画像

    ちなみに、事前にフォルダを作るのが面倒という方は、D 列の値を空欄にすれば、CMS Workspace のトップフォルダに格納されます。


    WinSCP を準備

    Marketing Cloud Engagement の SFTP は SFTP クライアントから操作できます。今回は SFTP クライアントとして WinSCP を利用しています。

    WinSCP を開いて、今回はサイト名を「MCE SFTP」としました。

    画像

    Marketing Cloud Engagement の SFTP へ接続します。
    ※ 既存の接続設定がある場合は、そちらをそのまま利用できます。

    画像

    SFTP にフォルダを作成し画像を配置

    続いて、「Import」配下に「mcn-cms-images」を作成します。

    画像

    そこへ、以下のサンプル画像 10 枚と manifest.csv を配置します。

    このファイルでそのまま試す場合は「Practice 10 Images」というフォルダが無いとエラーになります。

    /Import/mcn-cms-images/
    ├─ manifest.csv
    ├─ summer_hero.png
    ├─ autumn_sale.png
    ├─ winter_campaign.png
    ├─ spring_collection.png
    ├─ product_feature.png
    ├─ new_arrival.png
    ├─ member_offer.png
    ├─ event_banner.png
    ├─ brand_story.png
    └─ company_logo.png
    画像

    作業フォルダを作成

    次に PC 上に、以下のフォルダ「C:\MCN-CMS-Import\」を作成します。

    作成方法は C:\ を開いて、手動作成でも良いですが、PowerShell を開いて、以下を 1 行を実行しても良いです。

    New-Item -ItemType Directory -Path "C:\MCN-CMS-Import"
    画像

    そして、最終的には以下のようなものを目指していきます。

    C:\MCN-CMS-Import\
    ├─ create-cms-import.ps1
    ├─ download\
    ├─ work\
    └─ MCN_CMS_Import.zip

    ※ download と work に関しては、PowerShell が自動で作成するため、事前に作る必要はありません。


    PowerShell を作成

    続いて、以下の PowerShell コードを「create-cms-import.ps1」という名前で保存します。

    画像

    メモ帳で保存する方法

    1. Windowsの メモ帳 を開きます。

    2. 以下の PowerShell コードをすべて貼り付けます。
      ※ 皆さんが変更する可能性があるのは、頭にある 2 箇所だけです。
      - $WinSCPSite = "MCE SFTP"
      - $RemoteFolder = "/Import/mcn-cms-images"

    3. ファイル > 名前を付けて保存 を選択します。

    4. 保存先として、C:\MCN-CMS-Import\ のフォルダを指定します。

    5. ファイル名を「create-cms-import.ps1」にします。

    6. ファイルの種類 は「すべてのファイル」を選択します。

    7. 文字コードは「UTF-8」のままで問題ありません。

    8. 保存 をクリックします。

    9. 最終的に「C:\MCN-CMS-Import\create-cms-import.ps1」のファイルが作成されていれば OK です。
      ※ create-cms-import.ps1.txt になっていないことを確認してください。

    # ============================================================
    # Marketing Cloud Next
    # CMS Bulk Image Import ZIP Generator
    # MCE SFTP -> WinSCP -> CMS Import ZIP
    # manifest.csv: fileName,title,altText,folderPath
    # ============================================================
    
    $WinSCPSite = "MCE SFTP"
    $RemoteFolder = "/Import/mcn-cms-images"
    
    $BaseFolder = Split-Path -Parent $MyInvocation.MyCommand.Path
    $DownloadFolder = Join-Path $BaseFolder "download"
    $WorkFolder = Join-Path $BaseFolder "work"
    $OutputZip = Join-Path $BaseFolder "MCN_CMS_Import.zip"
    
    $Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
    
    $WinSCPPaths = @(
        "C:\Program Files (x86)\WinSCP\WinSCP.com",
        "C:\Program Files\WinSCP\WinSCP.com"
    )
    
    $WinSCPExe = $null
    foreach ($Path in $WinSCPPaths) {
        if (Test-Path $Path) {
            $WinSCPExe = $Path
            break
        }
    }
    
    if (-not $WinSCPExe) {
        Write-Error "WinSCP.com was not found."
        exit 1
    }
    
    Write-Host ""
    Write-Host "========================================"
    Write-Host "MCN CMS Import ZIP Generator"
    Write-Host "========================================"
    Write-Host ""
    
    if (Test-Path $DownloadFolder) { Remove-Item $DownloadFolder -Recurse -Force }
    if (Test-Path $WorkFolder) { Remove-Item $WorkFolder -Recurse -Force }
    if (Test-Path $OutputZip) { Remove-Item $OutputZip -Force }
    
    New-Item -ItemType Directory -Path $DownloadFolder -Force | Out-Null
    New-Item -ItemType Directory -Path $WorkFolder -Force | Out-Null
    
    $WinSCPScript = Join-Path $BaseFolder "winscp-download.txt"
    
    $WinSCPCommands = @"
    option batch abort
    option confirm off
    open "$WinSCPSite"
    cd "$RemoteFolder"
    lcd "$DownloadFolder"
    get *
    exit
    "@
    
    [System.IO.File]::WriteAllText(
        $WinSCPScript,
        $WinSCPCommands,
        $Utf8NoBom
    )
    
    Write-Host "Downloading files from MCE SFTP..."
    
    & $WinSCPExe "/script=$WinSCPScript"
    
    if ($LASTEXITCODE -ne 0) {
        Write-Error "SFTP download failed."
        Remove-Item $WinSCPScript -Force -ErrorAction SilentlyContinue
        exit 1
    }
    
    Remove-Item $WinSCPScript -Force -ErrorAction SilentlyContinue
    
    $ManifestPath = Join-Path $DownloadFolder "manifest.csv"
    
    if (-not (Test-Path $ManifestPath)) {
        Write-Error "manifest.csv was not found."
        exit 1
    }
    
    $Items = Import-Csv $ManifestPath
    
    if (-not $Items) {
        Write-Error "manifest.csv contains no records."
        exit 1
    }
    
    $Index = 1
    $CreatedCount = 0
    $SkippedCount = 0
    
    foreach ($Item in $Items) {
    
        $FileName = if ($null -ne $Item.fileName) { $Item.fileName.Trim() } else { "" }
        $Title = if ($null -ne $Item.title) { $Item.title.Trim() } else { "" }
        $AltText = if ($null -ne $Item.altText) { $Item.altText.Trim() } else { "" }
        $FolderPath = if ($null -ne $Item.folderPath) { $Item.folderPath.Trim() } else { "" }
    
        if (-not $FileName) {
            Write-Warning "Skipped a row because fileName is blank."
            $SkippedCount++
            continue
        }
    
        if (-not $Title) {
            Write-Warning "Skipped $FileName because title is blank."
            $SkippedCount++
            continue
        }
    
        $SourceImage = Join-Path $DownloadFolder $FileName
    
        if (-not (Test-Path $SourceImage)) {
            Write-Warning "File not found: $FileName"
            $SkippedCount++
            continue
        }
    
        $Extension = [System.IO.Path]::GetExtension($FileName).ToLower()
    
        switch ($Extension) {
            ".png"  { $MimeType = "image/png" }
            ".jpg"  { $MimeType = "image/jpeg" }
            ".jpeg" { $MimeType = "image/jpeg" }
            ".gif"  { $MimeType = "image/gif" }
            ".webp" { $MimeType = "image/webp" }
            default {
                Write-Warning "Unsupported image type: $FileName"
                $SkippedCount++
                continue
            }
        }
    
        $SafeTitle = $Title -replace '[\\/:*?"<>|]', '_'
        $FolderName = "{0:D3}_{1}" -f $Index, $SafeTitle
    
        $ContentFolder = Join-Path $WorkFolder $FolderName
        $MediaFolder = Join-Path $ContentFolder "_media"
    
        New-Item -ItemType Directory -Path $MediaFolder -Force | Out-Null
        Copy-Item $SourceImage (Join-Path $MediaFolder $FileName)
    
        $ContentBody = @{
            "sfdc_cms:media" = @{
                source = @{
                    type = "file"
                    mimeType = $MimeType
                }
            }
        }
    
        if ($AltText) {
            $ContentBody["altText"] = $AltText
        }
    
        $Content = @{
            type = "sfdc_cms__image"
            title = $Title
            contentBody = $ContentBody
        }
    
        $Json = $Content | ConvertTo-Json -Depth 10
        $JsonPath = Join-Path $ContentFolder "content.json"
    
        [System.IO.File]::WriteAllText(
            $JsonPath,
            $Json,
            $Utf8NoBom
        )
    
        if ($FolderPath) {
            $Meta = @{ path = $FolderPath }
            $MetaJson = $Meta | ConvertTo-Json -Depth 5
            $MetaPath = Join-Path $ContentFolder "_meta.json"
    
            [System.IO.File]::WriteAllText(
                $MetaPath,
                $MetaJson,
                $Utf8NoBom
            )
        }
    
        if ($FolderPath) {
            Write-Host "Created: $Title [$FileName] -> $FolderPath"
        } else {
            Write-Host "Created: $Title [$FileName] -> Workspace root"
        }
    
        $Index++
        $CreatedCount++
    }
    
    if ($CreatedCount -eq 0) {
        Write-Error "No CMS content was created."
        exit 1
    }
    
    Write-Host ""
    Write-Host "Creating CMS Import ZIP..."
    
    Compress-Archive `
        -Path "$WorkFolder\*" `
        -DestinationPath $OutputZip `
        -CompressionLevel Optimal
    
    Write-Host ""
    Write-Host "========================================"
    Write-Host "Completed"
    Write-Host "========================================"
    Write-Host "Created : $CreatedCount"
    Write-Host "Skipped : $SkippedCount"
    Write-Host ""
    Write-Host "Output:"
    Write-Host $OutputZip
    Write-Host ""
    Write-Host "Import this ZIP from:"
    Write-Host "Marketing Cloud Next"
    Write-Host "CMS Workspace > Manage > Import"
    Write-Host ""

    ※ 上記の PowerShell コードを参考にして、生成 AI にご自身のファイルの置き場(例:Amazon S3、Azure Blob Storage、Google Cloud Storage、独自 SFTP、ローカルフォルダ)に相応しいコードを作成させてください。


    PowerShell を実行

    PowerShell を開いて、以下のコマンドを実行して、作業フォルダへ移動します。

    cd C:\MCN-CMS-Import
    画像

    続いて、

    .\create-cms-import.ps1

    を実行すると、処理が走ります。

    画像

    PowerShell スクリプトの実行が無効になっている場合は、一時的な許可方法として、Set-ExecutionPolicy -Scope Process Bypass をコマンドで実行してから、再度、.\create-cms-import.ps1 を実行してください。

    ※ 会社 PC の場合は、組織のセキュリティポリシーに従ってください。


    ZIP が完成

    すべての処理が完了すると、以下の ZIP ファイルが生成されます。これがインポート用のファイルです。

    C:\MCN-CMS-Import\MCN_CMS_Import.zip

    画像

    この ZIP の中身は、以下のようになります。

    MCN_CMS_Import.zip
    ├─ 001_Summer_Hero/
    │  ├─ content.json
    │  ├─ _meta.json
    │  └─ _media/
    │     └─ summer_hero.png
    │
    ├─ 002_Autumn_Sale/
    │  ├─ content.json
    │  ├─ _meta.json
    │  └─ _media/
    │     └─ autumn_sale.png
    │
    ├─ 003_Winter_Campaign/
    │  ├─ content.json
    │  ├─ _meta.json
    │  └─ _media/
    │     └─ winter_campaign.png
    │
    ...

    つまり、人が画像ごとに JSON ファイルやフォルダを 1 件ずつ作成する必要はありません。

    PowerShell が manifest.csv の内容を読み取り、画像ごとに content.json、_meta.json、_media フォルダを自動生成します。

    例えば、Summer Hero の content.json は以下のようになります。

    {
      "title": "Summer Hero",
      "contentBody": {
        "altText": "Summer campaign hero image",
        "sfdc_cms:media": {
          "source": {
            "mimeType": "image/png",
            "type": "file"
          }
        }
      },
      "type": "sfdc_cms__image"
    }

    また、manifest.csv の folderPath に Practice 10 Images を指定している場合は、以下の _meta.json も自動生成されます。

    {
      "path": "Practice 10 Images"
    }

    これにより、インポート時に対象の画像を CMS Workspace 内の指定フォルダへ配置できます。


    Marketing Cloud Next へインポート

    最後に Marketing Cloud Next の CMS Workspace を開き、Manage ボタンをクリックして、「Import」を選択します。

    画像

    先ほど作成した MCN_CMS_Import.zip をアップロードして、「Publish all content after import」にチェックをしてインポートすることで、インポートと同時に、画像を公開することができます。

    画像

    正常に処理されれば、完了メールが飛んできます。

    画像

    その後、画面を F5 で更新すると 10 件の画像が Published(公開済み)として CMS Workspace へ登録されます。成功です。

    画像

    いかがでしたでしょうか。

    数件程度の画像であれば、CMS Workspace から 1 件ずつアップロードした方が早い場合もあります。

    一方で、数十件、数百件と登録数が増えてくると、今回のように ZIP ファイルを生成して一括インポートする方法の方が効率的です。

    私自身は上限まで試したことはありませんが、CMS のインポート機能では、1 回に最大 5,000 コンテンツをインポートできるとされています。

    また、Enhanced CMS Workspace には、1 Workspace あたり 300,000 コンテンツの上限があります。

    さらに、Marketing Cloud Next Growth / Advanced Editions では、CMS Storage として 10 GB + 2 GB × ユーザー数 が割り当てられています。

    そのため、大量の画像を継続的に保存する場合は、コンテンツ件数だけでなく、画像ファイルによって消費される CMS Storage の容量にも注意する必要があります。

    今回のように 10 件や 100 件程度の画像を一括登録する用途であれば、通常はコンテンツ件数の上限には十分余裕がありますが、高解像度の画像を大量に保存する場合は、ストレージ使用量もあわせて確認しておくとよいでしょう。

    今回は Marketing Cloud Engagement の SFTP を画像の保管場所として利用しましたが、重要なのは、複数の画像と登録情報から、Salesforce CMS のインポート形式に合わせた ZIP ファイルを生成することです。

    そのため、画像の保管場所が SFTP でなくても、同じ考え方で応用できます。以下のようなものが考えられます。

    • Amazon S3

    • Azure Blob Storage

    • Google Cloud Storage

    • 独自 SFTP

    • ローカルフォルダ(自分の PC 上のフォルダ)

    今後、大量の画像を Marketing Cloud Next へ登録する必要がある場合は、一つの方法として参考にしていただければと思います。

    今回は以上です。


    次の記事はこちら

    前回の記事はこちら

    私の note のトップページはこちら

     
     
     
    Salesforce Marketing Cloud、Agentforce、Data Cloud、Salesforce 認定資格に関する実践的な情報を発信しています。これらの記事が、皆さまの学習や日々の業務に少しでもお役に立てば幸いです。