
【第578回】 Marketing Cloud Next : 複数画像を一括でアップロード公開する
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 ファイルを生成できる素晴らしいツールを作成してくれました。
どちらも手軽に利用できますので、実際に試してみて、使いやすいと感じた方のツールをご利用ください。


このツールがあれば、私が説明した 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」という名前で保存します。

メモ帳で保存する方法
Windowsの メモ帳 を開きます。
以下の PowerShell コードをすべて貼り付けます。
※ 皆さんが変更する可能性があるのは、頭にある 2 箇所だけです。
- $WinSCPSite = "MCE SFTP"
- $RemoteFolder = "/Import/mcn-cms-images"ファイル > 名前を付けて保存 を選択します。
保存先として、C:\MCN-CMS-Import\ のフォルダを指定します。
ファイル名を「create-cms-import.ps1」にします。
ファイルの種類 は「すべてのファイル」を選択します。
文字コードは「UTF-8」のままで問題ありません。
保存 をクリックします。
最終的に「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 へ登録する必要がある場合は、一つの方法として参考にしていただければと思います。
今回は以上です。