My personal Delve Blog on my test tenant.

How do you migrate your Delve Blogs content to Modern SharePoint?

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.

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!

mm
5 4 votes
Article Rating
Subscribe
Notify of
guest

6 Comments
most voted
newest oldest
Inline Feedbacks
View all comments