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
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
}