This post was most recently updated on July 31st, 2022.
5 min read.You might’ve heard the news – Delve Blogs are on the way out, leaving your users without a blogging platform on Office 365, and their data homeless. And by homeless, I mean deleted pretty soon. And by pretty soon I mean really soon.
Might be a good time to start thinking about storing it somewhere else instead!
While we’re all still waiting to see whether Microsoft sticks to their ambitious schedule (partial content freeze in December 2019, total content freeze in January 2020, and finally, total content deletion in April 2020), and whether they’ll finally offer a migration option or not, it probably doesn’t hurt to get ready for the move by yourself – so I’m offering one way to export the content from your tenant’s Delve blogs!
This post is the third post in my impromptu, Delve Blogs-themed blog series.
- Part 1: Delve Blogs getting deprecated – what do?
- Part 2: How to export your Delve blogs content?
- Part 3: How to migrate your Delve Blogs content to Modern SharePoint? (this post)
That said, let’s get down to the business!
Migration ahead – what do?
Okay – first of all, figure out where you want to take your content. Maybe to WordPress. Perhaps just stash it all on GitHub. Or just stash it on a network drive somewhere.
But in the odd case that you’ll want to migrate all of your Delve Blogs’ content to a Modern SharePoint Communication site instead, lovingly hand-crafting it into a blogging center for your organization, I’ve got your back.
I’m posting a script that you can use to achieve this. However, in case Microsoft actually DOES bring out a cool migration solution, I’ll be reviewing it as well and see if it works better or not, and will document my findings in this article!
Migrating your blogs with PnP PowerShell
Note:
Microsoft published a new version of PnP PowerShell commandlet ConvertTo-PnPClientSidePage that also handles Delve blog posts. While this is offered without an SLA, it should still be useful for the migration.
There’s now also a full migration script (per-blog – not a tenant-wide solution as-is) available in the link below.
I haven’t given it a try, yet – if you do, let me know about your experiences!
See more here:
https://docs.microsoft.com/en-us/sharepoint/dev/transform/modernize-blogs#step-3b-transform-delve-blog-pages-into-modern-pages
The script below exports all of your Delve blog contents to a local temp folder then grabs the contents and shoves them onto a Modern SharePoint Communication site.
The script handles different authors, downloads, and reuploads pictures from Delve Blogs, and also saves the modified date properly.
It’ll grab a post that looks something like this:
And turn it into something like so:
You can remap pretty much any of the fields in the script (such as “file name” and titles), if you so wish.
(Never mind the contents of the post – it was originally created to debug the SharePoint search indexing issues plaguing Delve Blogs earlier this year)
The prerequisites to run the script:
You’ll need the following things to run this:
- SharePointPnPPowershellOnline PowerShell module
- At least SharePoint-admin credentials
And then you’re good to go:
PowerShell script to migrate Delve Blogs
function Make-ServerRelative ($path) {
return $path.Substring($path.IndexOf("sharepoint.com/")+14)
}
try {
# Variables for export
$creds = Get-Credential
$tenantadmin = "https://contoso-admin.sharepoint.com"
$outputfilepath = "c:\temp\delveblogexport.csv"
$tempfolder = "c:\temp"
# Variables for modern blog creation
$modernBlogUrl = "https://contoso.sharepoint.com/sites/blogposts"
# Connect to tenant to get blog sites
Connect-PnPOnline $tenantadmin -Credentials $creds
$sites = Get-PnPTenantSite -Template POINTPUBLISHINGPERSONAL#0
$resultsarray = @()
# Loop through sites to get details for blog
foreach($s in $sites){
Connect-PnPOnline $s.Url -Credentials $creds
$ctx = Get-PnPContext
$list = Get-PnPList -Identity "Pages"
$pagecount = $list.ItemCount
$contributor = Get-PnPGroupMembers -Identity "Contributors" | select Email
$items = Get-PnPListItem -List $list
$ctx.ExecuteQuery();
Write-Host "Exporting from: "$s.Url -ForegroundColor Yellow
foreach($i in $items){
$ctx.Load($i)
$ctx.Load($i.File)
$ctx.ExecuteQuery()
$path = $s.Url + "/pPg/" + $i.File.Name
$path = Make-ServerRelative $path
Write-Host "Exporting page "$path
$f = Get-PnPFile -Url $path -AsString
$lastmodified = (Get-PnPFile -Url $path).TimeLastModified
$o = ConvertFrom-Json $f
$title = $o.Title
$subTitle = $o.SubTitle
$content = ""
$filename = $i.File.Name
$headerImage = ""
$color = ""
$listItem = Get-PnPListItem -List $list -Id $i.Id
$ctx.Load($listItem)
$ctx.ExecuteQuery()
$listItem = $listItem.LastItemUserModifiedDate
$props = $o.ControlData.psobject.properties.name
$count = $props | measure-object
for ($index = 0; $index -lt $count.Count; $index++)
{
$propName = $props[$index]
Write-Host "Exporting item: "$index" "$propName" of type " $o.ControlData.$propName.ControlType
# Header content
if ($o.ControlData.$propName.ControlType -eq 4)
{
$content += "<h1>"+$o.ControlData.$propName.DataContext.Title+"</h1><h2>"+$o.ControlData.$propName.DataContext.Subtitle+"</h2>"
$headerImage = $o.ControlData.$propName.DataContext.ImageSource
continue;
}
# Text content
if ($o.ControlData.$propName.ControlType -eq 0) {
$content += $o.ControlData.$propName.DataContext.Value
}
# Picture content
if ($o.ControlData.$propName.ControlType -eq 1) {
$content += "<img src='"+$o.ControlData.$propName.DataContext.ImageSource+"' /><caption>"+$o.ControlData.$propName.DataContext.CaptionText+"</caption>"
}
# Document content
if ($o.ControlData.$propName.ControlType -eq 8) {
$content += "<a href='"+$o.ControlData.$propName.DataContext.OfficeDocumentDataContext.DocPath+"'>"+$o.ControlData.$propName.DataContext.Annotation+"</a>"
}
# Video content
if ($o.ControlData.$propName.ControlType -eq 9) {
$content += "<a href='"+$o.ControlData.$propName.DataContext.VideoSource+"'><caption><img src='"+$o.ControlData.$propName.DataContext.ImageSource+"' />"+$o.ControlData.$propName.DataContext.CaptionText+"</caption></a>"
}
}
if ($null -ne $headerImage -and $headerImage.IndexOf('#') -ge 0)
{
$color = $headerImage
}
else
{
try
{
$serverRelativeImageHeaderUrl = Make-ServerRelative $headerImage
$imgFile = Get-PnPFile -Url $serverRelativeImageHeaderUrl
Get-PnPFile -Url $serverRelativeImageHeaderUrl -Path $tempfolder -AsFile
}
catch {
Write-Host "There was likely no header image present, so couldn't export it."
}
}
# Add to the export object
$obj = New-Object PSObject
Add-Member -InputObject $obj -MemberType NoteProperty -Name DelveBlogUrl -Value $s.Url
Add-Member -InputObject $obj -MemberType NoteProperty -Name BlogPageCount -Value $pagecount
Add-Member -InputObject $obj -MemberType NoteProperty -Name Email -Value $contributor.Email
Add-Member -InputObject $obj -MemberType NoteProperty -Name LastModified -Value $lastmodified
Add-Member -InputObject $obj -MemberType NoteProperty -Name Title -Value $title
Add-Member -InputObject $obj -MemberType NoteProperty -Name Subtitle -Value $subTitle
Add-Member -InputObject $obj -MemberType NoteProperty -Name HeaderImage -Value $headerImage
Add-Member -InputObject $obj -MemberType NoteProperty -Name Content -Value $content
Add-Member -InputObject $obj -MemberType NoteProperty -Name FileName -Value $filename
Add-Member -InputObject $obj -MemberType NoteProperty -Name HeaderFile -Value $imgFile.Name
Add-Member -InputObject $obj -MemberType NoteProperty -Name ThemeColor -Value $color
$resultsarray += $obj
$obj = $null
}
}
# Export results
$resultsarray | Export-Csv -Path $outputfilepath -NoTypeInformation -Force
Write-Host "Export Complete" -ForegroundColor Green
# We're done with the export! Let's start the import.
try
{
$blogSite = New-PnPSite -Type CommunicationSite -Title Contoso -Url $modernBlogUrl
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Yellow
}
Disconnect-PnPOnline
Connect-PnPOnline $modernBlogUrl -Credentials $creds
$ctx = Get-PnPContext
$memberGroup = Get-PnPGroup -AssociatedMemberGroup
foreach ($o in $resultsarray)
{
# Add User to the site
Add-PnPUserToGroup -LoginName $o.Email -Identity $memberGroup
$ctx.ExecuteQuery()
$filename = $o.FileName.Replace(".pointpub",".aspx")
if ((Get-PnPClientSidePage -Identity $filename -ErrorAction SilentlyContinue | Measure-Object).Count -gt 0) {
Write-Host "Page already existed." -ForegroundColor White
$p = Get-PnPClientSidePage -Identity $filename
} else {
$p = Add-PnPClientSidePage -Name $filename
}
$ctx.ExecuteQuery()
$p.Save()
$imagepath = $tempfolder+"\"+$o.HeaderFile
$file = Add-PnPFile -Path $imagepath -Folder "Shared Documents" -UseWebDav
$imgUrl = (Make-ServerRelative ($ctx.Url+"/Shared Documents/"+$file.Name))
Write-Host "Page: " $p.PageTitle -ForegroundColor Blue
$p.ClearPage()
$p.Save()
$null = Add-PnPClientSideText -Page $p -Text $o.Content
$p.Save()
$p.Publish("Automatically published by DelveBlogs migration script!")
$null = Set-PnPClientSidePage -Identity $filename -Publish
$null = Set-PnPClientSidePage -Identity $filename -HeaderType Custom -ServerRelativeImageUrl $imgUrl -Publish
$result = Set-PnPListItem -List $p.PagesLibrary -Identity $p.PageListItem -Values @{ "Author" = $o.Email; "Editor" = $o.Email; "Modified" = $o.LastModified }
#-TranslateX 10.5 -TranslateY 11.0
$ctx.ExecuteQuery()
$weburl = $ctx.Url
Write-Host "Done: $weburl/SitePages/$filename" -ForegroundColor Cyan
}
Write-Host "Import Complete" -ForegroundColor Green
}
catch
{
Write-Host $_.Exception.Message -ForegroundColor Red
}
As per usual, this script is delivered as-is and without warranties. Might require tweaking, before it works for your use case. And of course, there are some styles and edge cases the script could manage better – I’ve got no special styles set up for captions, for example, and didn’t check whether the pictures in the body of the article are uploaded into OneDrive or Delve Blog site.
References
Crafting this script was a great refresher on a bunch of PowerShell-related tricks and gimmicks!
- If you need to set properties for a list item, Set-PnPListItem is your friend! What a flexible commandlet:
- Date formats in your source and target sites might not match. Then you need to format the dates, and this post is your friend:
- https://devblogs.microsoft.com/scripting/formatting-date-strings-with-powershell/
- Intellisense not working for Fluent components in Blazor project? Easy fix! - October 8, 2024
- winget is broken again. - October 1, 2024
- How to fix PowerToys FancyZones in Windows 11? - September 24, 2024