Skip to main content
I took out a part of the script that was breaking the html into sections based on h tags because it wasn't working effectively
Source Link
    #
    $csomPath = "C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.SharePointOnline.CSOM.16.1.19515.12000\lib\net45"
    $htmlPath = "<LocalPath>\Scripts\htmlagilitypack.1.11.17\lib\Net45"

    #IMport modules and types
    import-module SharepointPNPPowershellONline
    Add-Type -path $csomPath\Microsoft.SharePoint.Client.dll
    Add-Type -path $csomPath\Microsoft.SharePoint.Client.Runtime.dll
    Add-Type -Path $htmlPath\HtmlAgilityPack.dll

    #connect to SPO and on prem
    if (!$credspo){
    $credspo = get-credential -Message "Enter SPO credentials"}
    if (!$credOnP) {
    $credOnP = get-credential -message "Enter on Prem Credentials"}
    Connect-PnPOnline -Url https://<SharepointSPoURL> -Credentials $credspo

    $context = New-Object Microsoft.SharePoint.Client.ClientContext("<SPOnPremUrl>")

    $context.Credentials = $credOnP

    $web = $context.Web
    $context.Load($web)  #just a test to ensure connection
    $context.ExecuteQuery()

    $caml = new-object Microsoft.SharePoint.Client.CamlQuery 
    $caml.ViewXml = "<View>  
                <Query> 
                   <Where><Geq><FieldRef Name='Created' /><Value Type='DateTime'>2017-10-09T12:00:00Z</Value></Geq></Where> 
                </Query> 
          </View>"

    #pull the list of news posts to sync. only needed posts after Oct 2017
    $posts = $web.Lists.GetByTitle("Posts")
    if (!$idToSync){
    $postItems = $posts.GetItems($caml)}
    else {$postItems = $posts.GetItemById($idToSync)}
    $context.Load($posts)
    $context.Load($postitems)
    $context.ExecuteQuery()



    #these characters are not allowed in titles 
    $specialChar  =  '[#?/\///:/*/?/"/</>/|/#/%]'

    #iterate though the collection of news items
    foreach ($post in $postItems){

    $title = $Post["Title"] -replace $specialChar, " " #clean out the title
    if ($title.Length -gt 374){
    $title = $Title.substring(0,375)
    } #max length for a title is 400 char. using 375 to give myself some headroom

    $newPage = Add-PnPClientSidePage -Name $title -LayoutType Article 


    $html = $post.FieldValues.Body
    $sections = @()

    $start = 1
    $hIndex = 0

    while ($start -le $html.LastIndexOf("<h"))
    {
    #separating the body txt into sections based on h tags

    $hIndex = $html.IndexOf("<h",$start)
    $sub = $html.Substring($start-1,$hIndex - $start)
    $sections += $sub
    $start = $hIndex+1
    }
    $sI =  1

    "Posting $title with $($sections.count)"
    if ($sections.count -eq 0){$sections =  $html}



    foreach ($section in $sections){

    Add-PnPClientSidePageSection -Page $newPage -SectionTemplate OneColumn 

    Add-PnPClientSideText -Page $newPage -Section $sI$1 -text $section$html -Column 1
    $sI ++

    }

    $doc=  New-Object HTMLAgilityPack.HTMLDocument
    $doc.LoadHTML($html) 

    $img = $doc.DocumentNode.SelectNodes("//img")[0]   #being fancy - pull the first image to use for the thumbnail

    $newPage.ThumbnailUrl = $img.Attributes["src"].Value
    $Categories = ""

    $post.FieldValues.PostCategory | % {$Categories +="$($_.LookupValue); "}


    $newPage.PromoteAsNewsArticle()
    $newPage.save()

    $itemUPdate = Set-PnPListItem -List "Site Pages" -Identity $newPage.PageListItem.Id -Values @{"Categories0" = $Categories; "SyncedID" = $post.Id; "SyncDate" = get-date }
        #this is dumb but if you don't put the command above into a variable it fails with format-default error.
    remove-variable img #clear out the img var so if there's not a picture in the next post it won't get the old image


    }
    #
    $csomPath = "C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.SharePointOnline.CSOM.16.1.19515.12000\lib\net45"
    $htmlPath = "<LocalPath>\Scripts\htmlagilitypack.1.11.17\lib\Net45"

    #IMport modules and types
    import-module SharepointPNPPowershellONline
    Add-Type -path $csomPath\Microsoft.SharePoint.Client.dll
    Add-Type -path $csomPath\Microsoft.SharePoint.Client.Runtime.dll
    Add-Type -Path $htmlPath\HtmlAgilityPack.dll

    #connect to SPO and on prem
    if (!$credspo){
    $credspo = get-credential -Message "Enter SPO credentials"}
    if (!$credOnP) {
    $credOnP = get-credential -message "Enter on Prem Credentials"}
    Connect-PnPOnline -Url https://<SharepointSPoURL> -Credentials $credspo

    $context = New-Object Microsoft.SharePoint.Client.ClientContext("<SPOnPremUrl>")

    $context.Credentials = $credOnP

    $web = $context.Web
    $context.Load($web)  #just a test to ensure connection
    $context.ExecuteQuery()

    $caml = new-object Microsoft.SharePoint.Client.CamlQuery 
    $caml.ViewXml = "<View>  
                <Query> 
                   <Where><Geq><FieldRef Name='Created' /><Value Type='DateTime'>2017-10-09T12:00:00Z</Value></Geq></Where> 
                </Query> 
          </View>"

    #pull the list of news posts to sync. only needed posts after Oct 2017
    $posts = $web.Lists.GetByTitle("Posts")
    if (!$idToSync){
    $postItems = $posts.GetItems($caml)}
    else {$postItems = $posts.GetItemById($idToSync)}
    $context.Load($posts)
    $context.Load($postitems)
    $context.ExecuteQuery()



    #these characters are not allowed in titles 
    $specialChar  =  '[#?/\///:/*/?/"/</>/|/#/%]'

    #iterate though the collection of news items
    foreach ($post in $postItems){

    $title = $Post["Title"] -replace $specialChar, " " #clean out the title
    if ($title.Length -gt 374){
    $title = $Title.substring(0,375)
    } #max length for a title is 400 char. using 375 to give myself some headroom

    $newPage = Add-PnPClientSidePage -Name $title -LayoutType Article 


    $html = $post.FieldValues.Body
    $sections = @()

    $start = 1
    $hIndex = 0

    while ($start -le $html.LastIndexOf("<h"))
    {
    #separating the body txt into sections based on h tags

    $hIndex = $html.IndexOf("<h",$start)
    $sub = $html.Substring($start-1,$hIndex - $start)
    $sections += $sub
    $start = $hIndex+1
    }
    $sI =  1

    "Posting $title with $($sections.count)"
    if ($sections.count -eq 0){$sections =  $html}



    foreach ($section in $sections){

    Add-PnPClientSidePageSection -Page $newPage -SectionTemplate OneColumn 

    Add-PnPClientSideText -Page $newPage -Section $sI -text $section -Column 1
    $sI ++

    }

    $doc=  New-Object HTMLAgilityPack.HTMLDocument
    $doc.LoadHTML($html) 

    $img = $doc.DocumentNode.SelectNodes("//img")[0]   #being fancy - pull the first image to use for the thumbnail

    $newPage.ThumbnailUrl = $img.Attributes["src"].Value
    $Categories = ""

    $post.FieldValues.PostCategory | % {$Categories +="$($_.LookupValue); "}


    $newPage.PromoteAsNewsArticle()
    $newPage.save()

    $itemUPdate = Set-PnPListItem -List "Site Pages" -Identity $newPage.PageListItem.Id -Values @{"Categories0" = $Categories; "SyncedID" = $post.Id; "SyncDate" = get-date }
        #this is dumb but if you don't put the command above into a variable it fails with format-default error.
    remove-variable img #clear out the img var so if there's not a picture in the next post it won't get the old image


    }
    #
    $csomPath = "C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.SharePointOnline.CSOM.16.1.19515.12000\lib\net45"
    $htmlPath = "<LocalPath>\Scripts\htmlagilitypack.1.11.17\lib\Net45"

    #IMport modules and types
    import-module SharepointPNPPowershellONline
    Add-Type -path $csomPath\Microsoft.SharePoint.Client.dll
    Add-Type -path $csomPath\Microsoft.SharePoint.Client.Runtime.dll
    Add-Type -Path $htmlPath\HtmlAgilityPack.dll

    #connect to SPO and on prem
    if (!$credspo){
    $credspo = get-credential -Message "Enter SPO credentials"}
    if (!$credOnP) {
    $credOnP = get-credential -message "Enter on Prem Credentials"}
    Connect-PnPOnline -Url https://<SharepointSPoURL> -Credentials $credspo

    $context = New-Object Microsoft.SharePoint.Client.ClientContext("<SPOnPremUrl>")

    $context.Credentials = $credOnP

    $web = $context.Web
    $context.Load($web)  #just a test to ensure connection
    $context.ExecuteQuery()

    $caml = new-object Microsoft.SharePoint.Client.CamlQuery 
    $caml.ViewXml = "<View>  
                <Query> 
                   <Where><Geq><FieldRef Name='Created' /><Value Type='DateTime'>2017-10-09T12:00:00Z</Value></Geq></Where> 
                </Query> 
          </View>"

    #pull the list of news posts to sync. only needed posts after Oct 2017
    $posts = $web.Lists.GetByTitle("Posts")
    if (!$idToSync){
    $postItems = $posts.GetItems($caml)}
    else {$postItems = $posts.GetItemById($idToSync)}
    $context.Load($posts)
    $context.Load($postitems)
    $context.ExecuteQuery()



    #these characters are not allowed in titles 
    $specialChar  =  '[#?/\///:/*/?/"/</>/|/#/%]'

    #iterate though the collection of news items
    foreach ($post in $postItems){

    $title = $Post["Title"] -replace $specialChar, " " #clean out the title
    if ($title.Length -gt 374){
    $title = $Title.substring(0,375)
    } #max length for a title is 400 char. using 375 to give myself some headroom

    $newPage = Add-PnPClientSidePage -Name $title -LayoutType Article 


    $html = $post.FieldValues.Body
   

    Add-PnPClientSidePageSection -Page $newPage -SectionTemplate OneColumn 

    Add-PnPClientSideText -Page $newPage -Section $1 -text $html -Column 1


    $doc=  New-Object HTMLAgilityPack.HTMLDocument
    $doc.LoadHTML($html) 

    $img = $doc.DocumentNode.SelectNodes("//img")[0]   #being fancy - pull the first image to use for the thumbnail

    $newPage.ThumbnailUrl = $img.Attributes["src"].Value
    $Categories = ""

    $post.FieldValues.PostCategory | % {$Categories +="$($_.LookupValue); "}


    $newPage.PromoteAsNewsArticle()
    $newPage.save()

    $itemUPdate = Set-PnPListItem -List "Site Pages" -Identity $newPage.PageListItem.Id -Values @{"Categories0" = $Categories; "SyncedID" = $post.Id; "SyncDate" = get-date }
        #this is dumb but if you don't put the command above into a variable it fails with format-default error.
    remove-variable img #clear out the img var so if there's not a picture in the next post it won't get the old image


    }
Source Link

I figured it out! SharepointPNPPowershell to the rescue!

Here's the script I wrote to keep the two sites in sync: param ($credspo, $credW3, $idToSync)

    #
    $csomPath = "C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.SharePointOnline.CSOM.16.1.19515.12000\lib\net45"
    $htmlPath = "<LocalPath>\Scripts\htmlagilitypack.1.11.17\lib\Net45"

    #IMport modules and types
    import-module SharepointPNPPowershellONline
    Add-Type -path $csomPath\Microsoft.SharePoint.Client.dll
    Add-Type -path $csomPath\Microsoft.SharePoint.Client.Runtime.dll
    Add-Type -Path $htmlPath\HtmlAgilityPack.dll

    #connect to SPO and on prem
    if (!$credspo){
    $credspo = get-credential -Message "Enter SPO credentials"}
    if (!$credOnP) {
    $credOnP = get-credential -message "Enter on Prem Credentials"}
    Connect-PnPOnline -Url https://<SharepointSPoURL> -Credentials $credspo

    $context = New-Object Microsoft.SharePoint.Client.ClientContext("<SPOnPremUrl>")

    $context.Credentials = $credOnP

    $web = $context.Web
    $context.Load($web)  #just a test to ensure connection
    $context.ExecuteQuery()

    $caml = new-object Microsoft.SharePoint.Client.CamlQuery 
    $caml.ViewXml = "<View>  
                <Query> 
                   <Where><Geq><FieldRef Name='Created' /><Value Type='DateTime'>2017-10-09T12:00:00Z</Value></Geq></Where> 
                </Query> 
          </View>"

    #pull the list of news posts to sync. only needed posts after Oct 2017
    $posts = $web.Lists.GetByTitle("Posts")
    if (!$idToSync){
    $postItems = $posts.GetItems($caml)}
    else {$postItems = $posts.GetItemById($idToSync)}
    $context.Load($posts)
    $context.Load($postitems)
    $context.ExecuteQuery()



    #these characters are not allowed in titles 
    $specialChar  =  '[#?/\///:/*/?/"/</>/|/#/%]'

    #iterate though the collection of news items
    foreach ($post in $postItems){

    $title = $Post["Title"] -replace $specialChar, " " #clean out the title
    if ($title.Length -gt 374){
    $title = $Title.substring(0,375)
    } #max length for a title is 400 char. using 375 to give myself some headroom

    $newPage = Add-PnPClientSidePage -Name $title -LayoutType Article 


    $html = $post.FieldValues.Body
    $sections = @()

    $start = 1
    $hIndex = 0

    while ($start -le $html.LastIndexOf("<h"))
    {
    #separating the body txt into sections based on h tags

    $hIndex = $html.IndexOf("<h",$start)
    $sub = $html.Substring($start-1,$hIndex - $start)
    $sections += $sub
    $start = $hIndex+1
    }
    $sI =  1

    "Posting $title with $($sections.count)"
    if ($sections.count -eq 0){$sections =  $html}



    foreach ($section in $sections){

    Add-PnPClientSidePageSection -Page $newPage -SectionTemplate OneColumn 

    Add-PnPClientSideText -Page $newPage -Section $sI -text $section -Column 1
    $sI ++

    }

    $doc=  New-Object HTMLAgilityPack.HTMLDocument
    $doc.LoadHTML($html) 

    $img = $doc.DocumentNode.SelectNodes("//img")[0]   #being fancy - pull the first image to use for the thumbnail

    $newPage.ThumbnailUrl = $img.Attributes["src"].Value
    $Categories = ""

    $post.FieldValues.PostCategory | % {$Categories +="$($_.LookupValue); "}


    $newPage.PromoteAsNewsArticle()
    $newPage.save()

    $itemUPdate = Set-PnPListItem -List "Site Pages" -Identity $newPage.PageListItem.Id -Values @{"Categories0" = $Categories; "SyncedID" = $post.Id; "SyncDate" = get-date }
        #this is dumb but if you don't put the command above into a variable it fails with format-default error.
    remove-variable img #clear out the img var so if there's not a picture in the next post it won't get the old image


    }