SharePoint Online - Create the classic mode Theme available into the Modern Site

Steel Contributor

Into a previous message, we saw how to configure and implement the Modern Site Theme using a PowerShell script:

The question of the classical Theme management, is due to the presence of the link "Classic change the look options" into the modern site theme management screen.

That option will send you to the old SharePoint 2013 "Change the Look" solution.

From an admin point of view, the Theme management is different between Modern and Classic:

  • Modern site:
    • At the tenant level:
      • Activation of the Theme selection
      • Remove the built-in color selection
      • Add the new Corporate Theme with the custom colorset
    • At the site collection level:
      • Nothing to do
  • Classic site:
    • At the tenant level: Nothing to do
    • At the site collection level: Nothing to do except the specific file upload
    • At the SPWeb level (so for each subsite)
      • Creation of the Theme
      • Deletion of the other basic selection

That is interesting to delegate the color management to the site owner, but always let them move back to the corporate colors if they want to.

 

So from the Tenant admin point of view, it's useful to have a solution to configure directly the site collections (root site and subsites) directly with a selection based on filtering.

It could reconfigure all the SharePoint standard sites or the group site, … depending of the filter you will apply into.

You need also to prepare the resource files (spconf, spcolor, backgroundfile and logo file).

 

The PowerShell script you can use and adapt as you need was used to reset more than 2000 site collections with a huge number of subsites without issue.

 

[string]$username = "admin@tenant.onmicrosoft.com"
[string]$PwdTXTPath = "C:\SECUREDPWD\ExportedPWD-$($username).txt"

$secureStringPwd = ConvertTo-SecureString -string (Get-Content $PwdTXTPath)
$adminCreds = New-Object System.Management.Automation.PSCredential $username, $secureStringPwd

[string]$ThemeFileFolder = "D:\THEME_FILES\CORP_SPO_THEME\"
[string]$ThemeName = "CORP Theme"
[string]$AssetListNameURL = "SiteAssets"
[string]$CORPFoldername = "CORPTheme"
[string]$IntranetMasterPage = "seattle.master"
[string]$ExtranetMasterPage = "oslo.master"

[string]$ThemeFileSPFont = "Fontscheme-CORP.spfont"
[string]$ThemeFileSPColor = "Palette-CORP.spcolor"
[string]$ThemeFileLogo = "logo_CORP.png"
[string]$ThemeFileIntranetBG = "INTRANET_CORP.jpg"
[string]$ThemeFileExtranetBG = "EXTRANET_CORP.jpg"

function Load-DLLandAssemblies
{
	[string]$defaultDLLPath = ""

	# Load assemblies to PowerShell session 

	$defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.dll"
	[System.Reflection.Assembly]::LoadFile($defaultDLLPath)

	$defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.Runtime.dll"
	[System.Reflection.Assembly]::LoadFile($defaultDLLPath)

	$defaultDLLPath = "C:\Program Files\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.Online.SharePoint.Client.Tenant.dll"
	[System.Reflection.Assembly]::LoadFile($defaultDLLPath)
}

function IfThemeExists($Myctx, $list, $themeName)
{
	[int]$ReturnValue = 0
	$caml="<View><Query><Where><Eq><FieldRef Name='Name' /><Value Type='Text'>$themeName</Value></Eq></Where></Query></View>";
	$cquery = New-Object Microsoft.SharePoint.Client.CamlQuery
	$cquery.ViewXml=$caml
	$listItems = $list.GetItems($cquery)
	$Myctx.Load($listItems)
	$Myctx.ExecuteQuery()
	if($listItems.Count > 0)
	{
		$ReturnValue = 0;
	}
	else
	{
		foreach ($targetListItem in $listItems)
		{
			$ReturnValue = $targetListItem["ID"]
		}
	}
	return $ReturnValue
}

function Upload-Specific-File($CompleteFilePath, $spoWebContext, $MyfolderURL )
{	
	$Localfile = get-item $CompleteFilePath
	
	[string]$MyNewFileURL = -join($MyfolderURL, "/", $Localfile.Name)
	write-host "CORP File URL Uploaded:", $Localfile.FullName, " - Url:", $MyNewFileURL
	[Microsoft.SharePoint.Client.File]::SaveBinaryDirect($spoWebContext, $MyNewFileURL, $Localfile.OpenRead(), $true)
}

#Custom Function to Add a new Theme in the old classic format
Function AddNewCorpThemeInSPweb($myctx, $myCurrentWeb, [string]$RootOrSubstite, [string]$RootSiteRelativeURL, [string]$ExtranetSharing)
{
    Try
	{
		[string]$tempSourcePath = ""
		[string]$tempDestinationPathSiteAsset = ""
		[string]$tempDestinationPathTheme = ""
		[string]$masterPageUrl = ""
		$myUploadedFile
		
		[string]$tempPathBG = ""
		[string]$tempPathFont = ""
		[string]$tempPathPalette = ""

		$tempDestinationPathSiteAsset = -join($myCurrentWeb.ServerRelativeUrl, "/", $AssetListNameURL, "/", $CORPFoldername)
		$tempDestinationPathTheme = -join($myCurrentWeb.ServerRelativeUrl, "/_catalogs/theme/15")

		Write-Host " ---------------------------------------------- "
		Write-Host " >>>> UPLOAD THE THEME FILES INTO THE ROOT SITE: ", $myCurrentWeb.ServerRelativeUrl  -foregroundcolor yellow
		#Deploy Custom Logo
		$tempSourcePath = -join($ThemeFileFolder, $ThemeFileLogo)
		Upload-Specific-File  $tempSourcePath $myctx $tempDestinationPathSiteAsset
		Write-Host "     >>>> file uploaded from:", $tempSourcePath, "- to:", $tempDestinationPathSiteAsset

		#Deploy BackGround
		$tempSourcePath = -join($ThemeFileFolder, $ThemeFileIntranetBG)
		Upload-Specific-File  $tempSourcePath $myctx $tempDestinationPathSiteAsset
		Write-Host "     >>>> file uploaded from:", $tempSourcePath, "- to:", $tempDestinationPathSiteAsset
		
		$tempSourcePath = -join($ThemeFileFolder, $ThemeFileExtranetBG)
		Upload-Specific-File  $tempSourcePath $myctx $tempDestinationPathSiteAsset
		Write-Host "     >>>> file uploaded from:", $tempSourcePath, "- to:", $tempDestinationPathSiteAsset
		if($ExtranetSharing -eq "Disabled")
		{
			$tempPathBG =  -join($tempDestinationPathSiteAsset, "/", $ThemeFileIntranetBG)
		}
		else
		{
			$tempPathBG =  -join($tempDestinationPathSiteAsset, "/", $ThemeFileExtranetBG)
		}
		
		#Deploy Custom Color
		$tempSourcePath = -join($ThemeFileFolder, $ThemeFileSPColor)
		Upload-Specific-File  $tempSourcePath $myctx $tempDestinationPathSiteAsset
		Write-Host "     >>>> file uploaded from:", $tempSourcePath, "- to:", $tempDestinationPathSiteAsset
		$tempPathPalette =  -join($tempDestinationPathTheme, "/", $ThemeFileSPColor)
		if($RootOrSubstite -eq "RootSite")
		{
			Upload-Specific-File  $tempSourcePath $myctx $tempDestinationPathTheme
			Write-Host "     >>>> file uploaded from:", $tempSourcePath, "- to:", $tempDestinationPathTheme
			$tempPathPalette =  -join($tempDestinationPathTheme, "/", $ThemeFileSPColor)
		}
		else
		{
			$tempPathPalette =  -join($RootSiteRelativeURL, "/_catalogs/theme/15/", $ThemeFileSPColor)
		}
		
		#Deploy Custom Font
		$tempSourcePath = -join($ThemeFileFolder, $ThemeFileSPFont)
		Upload-Specific-File  $tempSourcePath $myctx $tempDestinationPathSiteAsset
		Write-Host "     >>>> file uploaded from:", $tempSourcePath, "- to:", $tempDestinationPathSiteAsset
		if($RootOrSubstite -eq "RootSite")
		{
			Upload-Specific-File  $tempSourcePath $myctx $tempDestinationPathTheme
			Write-Host "     >>>> file uploaded from:", $tempSourcePath, "- to:", $tempDestinationPathTheme
			$tempPathFont =  -join($tempDestinationPathTheme, "/", $ThemeFileSPFont)
		}
		else
		{
			$tempPathFont =  -join($RootSiteRelativeURL, "/_catalogs/theme/15/", $ThemeFileSPFont)
		}

		if($ExtranetSharing -eq "Disabled")
		{
			$masterPageUrl = -join($myCurrentWeb.ServerRelativeUrl, "/_catalogs/masterpage/", $IntranetMasterPage)
		}
		else
		{
			$masterPageUrl = -join($myCurrentWeb.ServerRelativeUrl, "/_catalogs/masterpage/", $ExtranetMasterPage)
		}
		
		Write-Host " ---------------------------------------------- "  -foregroundcolor green
		Write-Host "     ===>> tempPathPalette:", $tempPathPalette   -foregroundcolor green
		Write-Host "     ===>> tempPathFont:", $tempPathFont  -foregroundcolor green
		Write-Host "     ===>> tempPathBG:", $tempPathBG  -foregroundcolor green
		Write-Host "     ===>> masterPageUrl:", $masterPageUrl  -foregroundcolor green
		Write-Host " ---------------------------------------------- "  -foregroundcolor green

		$themesOverviewList = $myCurrentWeb.GetCatalog(124);
		$myctx.Load($themesOverviewList);
		$myctx.ExecuteQuery();

		$IDThemeCorp = IfThemeExists -Myctx $myctx -list $themesOverviewList -themeName $ThemeName
		if ($IDThemeCorp -eq 0)
		{
			# Create new theme entry.
			$itemInfo  = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
			$item = $themesOverviewList.AddItem($itemInfo)
			$item["Name"] = $ThemeName;
			$item["Title"] = $ThemeName;
			$item["ThemeUrl"] = $tempPathPalette;
			$item["FontSchemeUrl"] = $tempPathFont;
			$item["ImageUrl"] = $tempPathBG;
			$item["MasterPageUrl"] = $MasterPageUrl ;
			$item["DisplayOrder"] = 1;
			$item.Update()
			$myctx.ExecuteQuery();
		}
		else
		{
			[Microsoft.SharePoint.Client.ListItem]$listItem = $themesOverviewList.GetItemById($IDThemeCorp)
			$listItem["Name"] = $ThemeName;
			$listItem["Title"] = $ThemeName;
			$listItem["ThemeUrl"] = $tempPathPalette;
			$listItem["FontSchemeUrl"] = $tempPathFont;
			$listItem["ImageUrl"] = $tempPathBG;
			$listItem["MasterPageUrl"] = $MasterPageUrl ;
			$listItem["DisplayOrder"] = 1;
			$listItem.Update()
			$myctx.ExecuteQuery()		
		}
	}
    Catch [Exception]
	{
		Write-host " >>>> ERROR MESSAGE:", $_.Exception.Message -f Red
	}
}

function Get-SPOSubWebs
{
	Param( 
        [Microsoft.SharePoint.Client.ClientContext]$Context, 
        [Microsoft.SharePoint.Client.Web]$RootWeb,
		[string]$RootSiteRelURL,
		[string]$ExtranetMode
    ) 
	
	$Webs = $RootWeb.Webs
	$Context.Load($Webs)
	$Context.ExecuteQuery()
	ForEach ($sWeb in $Webs)
	{
		Write-host "   ====>> SubSite:", $sWeb.URL -ForegroundColor red
		AddNewCorpThemeInSPweb $Context $sWeb "subsite" $RootSiteRelURL $ExtranetMode

		Get-SPOSubWebs -RootWeb $sWeb -Context $Context -RootSiteRelURL $RootSiteRelURL $ExtranetMode
	} 
} 

cls
Write-Host " ---------------------------------------------- "
Load-DLLandAssemblies
Write-Host " ---------------------------------------------- "

Connect-SPOService -Url https://tenant-admin.sharepoint.com -credential $adminCreds -ErrorAction SilentlyContinue -ErrorVariable Err

#Retrieve all site collection infos
#Get-SPOSite -Template GROUP#0 #(Group Sites)
#Get-SPOSite -Template POINTPUBLISHINGTOPIC#0  #(Video Portal Sites)
#$sitesInfo = Get-SPOSite -Identity https://tenant.sharepoint.com/sites/testsitecoll  | Sort-Object -Property url | Select *
#$sitesInfo = Get-SPOSite -Template "STS#0" -Limit ALL | Sort-Object -Property url | Select *

$sitesInfo = Get-SPOSite -Template "STS#0" -Filter  {Url -like "https://tenant.sharepoint.com/sites/a*"}  -Limit ALL | Sort-Object -Property url | Select *

[int]$i = 1;
[string]$ExternalSharingSetting = ""

Write-Host "--------------------------------------------------------------------------------------------"
#Retrieve and print all sites
foreach ($site in $sitesInfo)
{
	$ExportAllUserLogin = ""
	Write-Host "SiteColl Number:", $i, "- of:", $sitesInfo.Count;
    $i += 1;
    
    Write-Host "--------------------------------------------------------------------------------------------"
	Write-Host "SPO Site collection:", $site.Url, "- Title:", $site.Title
	Write-Host "   => External Sharing:", $site.SharingCapability
	Write-Host "   => Site Template Used:", $site.Template
	Write-Host "   => Storage Quota:", $site.StorageQuota, "- Storage used:", $site.StorageUsageCurrent
	Write-Host "   => Percent Usage:", $($site.StorageUsageCurrent / $site.StorageQuota * 100), "%"
	Write-Host "   => Resource Quota:", $site.ResourceQuota, "- Resource used:", $site.ResourceUsageCurrent
    Write-Host "--------------------------------------------------------------------------------------------"

	#Set-SPOsite -Identity $site.Url -DenyAddAndCustomizePages 0 #Non mandatory but could be useful for the group sites

	$mySitectx = New-Object Microsoft.SharePoint.Client.ClientContext($site.Url)
	$mySitectx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($adminCreds.UserName, $adminCreds.Password)
	$mySitectx.RequestTimeout = 1000000 # milliseconds
	$myCurrentWeb = $mySitectx.Web
	$mySitectx.Load($myCurrentWeb)
	$mySitectx.ExecuteQuery()

	$ExternalSharingSetting = $site.SharingCapability

	AddNewCorpThemeInSPweb $mySitectx $myCurrentWeb "RootSite" $myCurrentWeb.ServerRelativeUrl $ExternalSharingSetting
	Get-SPOSubWebs $mySitectx $myCurrentWeb $myCurrentWeb.ServerRelativeUrl $ExternalSharingSetting
} 

Write-Host " ---------------------------------------------- "

The source sites used to build that script are

This solution can also adapt it into your provisioning solution creating the Office 365 Teams or Groups.

 

Fabrice Romelard

1 Reply