My personal Delve Blog on my test tenant.

How to migrate your Delve Blogs content to Modern SharePoint?

Reading Time: 6 minutes.

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.

That said, let’s get down to the business!

Migration ahead – what do?

Okay – first of all figure out where do 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 on this article!

Migrating your blogs with PnP PowerShell

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!

Antti K. Koskela

Antti Koskela is a proud digital native nomadic millennial full stack developer (is that enough funny buzzwords? That's definitely enough funny buzzwords!), who works as a Solutions Architect for Valo Intranet, the product that will make you fall in love with your intranet. Working with the global partner network, he's responsible for the success of Valo deployments happening all around the world.

He's been a developer from 2004 (starting with PHP and Java), and he's been bending and twisting SharePoint into different shapes since MOSS. Nowadays he's not only working on SharePoint, but also on .NET projects, Azure, Office 365 and a lot of other stuff.

This is his personal professional (e.g. professional, but definitely personal) blog.
mm

1
Leave a Reply

avatar
5000
1 Comment threads
0 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
0 Comment authors
Recent comment authors
  Subscribe  
newest oldest most voted
Notify of