ディスカッション掲示板のデータ移行 サンプル スクリプトについて

こんにちは SharePoint サポートの森 健吾 (kenmori) です。今回の投稿では SharePoint Online または SharePoint Server 2013 において、ディスカッション掲示板が、ビューやページ カスタマイズによって壊れてしまった時などに有効な、同一サイト内でのデータ移行を実施するスクリプトを紹介します。

ディスカッション掲示板は特殊なリスト

ディスカッション掲示板は非常に特殊なリストです。このリスト自身が、標準リストをベースに作りこまれたリスト定義であり、特殊な機能を実装するために標準の設定変更やカスタマイズを想定していない場合があります。そのため、ビューを高度にカスタマイズすると、下記の様に予期せぬトラブルが発生する可能性があります。

・SharePoint Designer でビュー / Web パーツを編集し、保存すると破損した。
・既定のリスト ビューを再作成しようとしたが、元のビューが作成できなかった。

もし、ご要件上、どうしても高度なカスタマイズを組み込む場合は、テスト環境で空のディスカッション掲示板を作成して実績がある手順を作り、その上で手順書通りに本番環境に適用するなど、細心の注意を払う必要があります。また、できる限りスタイル シートや JavaScript など外部的な変更にとどまることも重要です。

ディスカッション掲示板に組み込まれた既定のビューは、一度破損すると元に戻りません。特に、フラット形式のビューが破損すると致命的な問題になります。フラット形式のビューは、ユーザーはディスカッションに対する返信を入力できる唯一のビューですので、このビューが削除されるとディスカッション掲示板の運用が厳しくなります。

対処策として様々な方法を検討できますが、PowerShell などで既存のビューと同じ値を復元しても元に戻りません。SharePoint Designer で同じパブリック プロパティにそろえても同様です。
ディスカッション掲示板の特殊なビューは、リスト作成時にスキーマ定義をもとに作られるものが唯一の正しい作成方法のようです。

 

ディスカッション掲示板のビューが壊れたときの対処

最も確実な方法は、ディスカッション掲示板を再作成した後、元のディスカッション掲示板内にあったデータを移行する方法です。
下記のサンプル コードは SharePoint Online に対応するために、CSOM (クライアント サイド オブジェクト モデル) で記載しており、PowerShell で実行できる形式にしております。

事前準備

下記の準備事項が、スクリプト実行の前提となります。

1. PowerShell を実行する端末には、事前に SharePoint Online Client SDK をインストールしておきます。

  タイトル : SharePoint Online Client Components SDK
  アドレス : https://www.microsoft.com/en-us/download/details.aspx?id=42038

2. 管理者権限で PowerShell を起動し、*.ps1 ファイルを実行するために、PowerShell の実行ポリシーを変更します。

 Set-ExecutionPolicy RemoteSigned

 

実行手順

1. 新しいディスカッション掲示板を作成します。
2. 本ブログ下部のサンプル スクリプトを MigrateDiscussionBoard.ps1 として保存し、任意のフォルダ (例. C:\TEMP) に配置します。
3. PowerShell のカレント ディレクトリを配置先のフォルダに指定します。

 cd c:\TEMP

4. 下記のようにスクリプトを実行します。

 .\MigrateDiscussionBoard.ps1 -siteUrl https://tenant.sharepoint.com/sites/site -srcListTitle "移行元掲示板名" -destListTitle "移行先掲示板名"

5. 実行後、下記の様にユーザー名とパスワードを聞かれますのでそれぞれタイプし、[Enter] を押します。

Please input user name :

Please input password :

6. 実行完了まで待機します。
7. PowerShell が再度入力可能状況になったら終了です。移行先の掲示板でデータが移行されていることをご確認ください。

 Param(
 $siteUrl, $srcListTitle, $destListTitle
)

If ($siteUrl -eq $null)
{
   Write-Host "Example)"
   Write-Host ">.\MigrateDiscussionBoard.ps1 -siteUrl https://tenant.sharepoint.com/sites/site -srcListTitle discussion1 -destListTitle discussion2"
   return
}

# 必要なアセンブリをロードします
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")

# SharePoint に接続します
$context = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)

#ユーザー名入力を促します。
Write-Host "Please input user name : "
$username = read-host

# パスワード入力を促します。
Write-Host "Please input password : "
$password = read-host -assecurestring

$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password) 

# On-Premise で使用する場合 (例. Windows 認証) は、例えば下記の様に権限を指定ください。
#$credentials = New-Object System.Net.NetworkCredential("Administrator", "password", "domain")

$context.Credentials = $credentials
$srclist = $context.Web.Lists.GetByTitle($srcListTitle)
$destlist = $context.Web.Lists.GetByTitle($destListTitle)
$context.Load($srclist)
$context.Load($destlist)
$context.ExecuteQuery()
$camlquery = New-Object Microsoft.SharePoint.Client.CamlQuery
$items = $srclist.GetItems($camlquery)
 
$context.Load($items)
$context.ExecuteQuery()

foreach ($item in $items)
{
  $context.Load($item)
  $context.ExecuteQuery()

  $discussionitem = [Microsoft.SharePoint.Client.Utilities.Utility]::CreateNewDiscussion($context, $destlist, $item.Item("Title"))
  $discussionitem.Update()
  $context.ExecuteQuery()

  $messagecamlquery = New-Object Microsoft.SharePoint.Client.CamlQuery
  $messagecamlquery.ViewXml = "<View Scope='Recursive'><Query><Where><Eq><FieldRef Name='ParentFolderId'/><Value Type='Integer'>" + $item.Id + "</Value></Eq></Where></Query></View>"
  $messageitems = $srclist.GetItems($messagecamlquery)
  $context.Load($messageitems)
  $context.ExecuteQuery()  

  $lastModified = $item.Item("Modified")

  foreach ($messageitem in $messageitems)
  {
    $replyitem = [Microsoft.SharePoint.Client.Utilities.Utility]::CreateNewDiscussionReply($context, $discussionitem)
    $replyitem.Item("Body") = $messageitem.Item("Body")
    $replyitem.Item("Author") = $messageitem.Item("Author")
    $replyitem.Item("Editor") = $messageitem.Item("Editor")
    $replyitem.Item("Created") = $messageitem.Item("Created")
    $replyitem.Item("Modified") = $messageitem.Item("Modified")
    $replyitem.Update() 

    if ($lastModified -lt $messageitem.Item("Modified"))
    {
      $lastModified = $messageitem.Item("Modified")
    }
  }

  $discussionitem.Item("Body") = $item.Item("Body")
  $discussionitem.Item("Author") = $item.Item("Author")
  $discussionitem.Item("Editor") = $item.Item("Editor")
  $discussionitem.Item("Created") = $item.Item("Created")
  $discussionitem.Item("Modified") = $item.Item("Modified")
  $discussionitem.Item("DiscussionLastUpdated") = $lastModified
  $discussionitem.Item("LastReplyBy") = $item.Item("LastReplyBy")
  $discussionitem.Item("BestAnswerId") = $item.Item("BestAnswerId")
  $discussionitem.Item("IsAnswered") = $item.Item("IsAnswered")
  $discussionitem.Update()
  $context.ExecuteQuery()
}

2016.09.01 更新
ディスカッションの更新日時 (DiscussionLastUpdated) の移行処理を修正

参考情報

タイトル : Utility.CreateNewDiscussion method
アドレス : https://msdn.microsoft.com/en-us/library/office/microsoft.sharepoint.client.utilities.utility.createnewdiscussion(v=office.15).aspx

タイトル : Utility.CreateNewDiscussionReply method
アドレス : https://msdn.microsoft.com/en-us/library/office/microsoft.sharepoint.client.utilities.utility.createnewdiscussionreply(v=office.15).aspx

タイトル : SharePoint Online に対して PowerShell を使用する方法
アドレス : https://blogs.technet.com/b/sharepoint_support/archive/2014/10/20/sharepoint-online-powershell.aspx

上記コードは移行のための最低限の機能しか保持しておりません。確認できている範囲で、上記コードは返信の返信などネストにも対応しておらず、返信はすべて 1 階層で表示されるようになっています。
もし、より詳細な移行シナリオがある場合は加筆修正した上で、十分にテストした上でご検討ください。