Set-SharePointSiteExperience.ps1

<#PSScriptInfo
 
.VERSION 1.0
 
.GUID e8d7e821-6902-44b7-a5a6-a22e05000332
 
.AUTHOR Aaron Guilmette
 
.COMPANYNAME Microsoft
 
.COPYRIGHT 2020
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI https://www.undocumented-features.com/2019/05/28/switch-sharepoint-online-lists-between-classic-and-modern-experience/
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
.DESCRIPTION
If you need to update/revert your SharePoint Online libraries and lists to Classic experience, look no further. This script will let you switch between Classic and Modern experiences for one or more sites in your tenant.
 
.PRIVATEDATA
 
#>


<#
.SYNOPSIS
Switch SharePoint Online site experience between modern and classic.
 
.PARAMETER AllSites
Select all sites in tenant except Group sites (default).
 
.PARAMETER WebAndSiteLevel
Configure Classic or Modern every which way but loose. This option is mentioned
in the documentation but does not appear to work at this time.
 
.PARAMETER Credential
Standard PSCredential object.
 
.PARAMETER Experience
Switch between Classic and Modern library/list experiences.
 
.PARAMETER IncludeGroupSites
Attempt to update the Experience type for Office 365 Group sites (will most
likely not complete successfully).
 
.PARAMETER InputFile
List of SharePoint Sites. You can run (Get-SPOSite).Url to get a list of sites
in your tenant.
 
.PARAMETER ListLevel
Update the ListExperienceOptions on each list item.
 
.PARAMETER Site
One or more SharePoint Online site URLs.
 
.PARAMETER SkipValidation
By default, script will check to ensure sites exist before attempting to update.
If you are attempting to update a large list of sites, you may wish to skip this
step.
 
.PARAMETER Tenant
Tenant name as either 'tenant.onmicrosoft.com' or 'tenant'
 
.EXAMPLE
.\Set-SharePointSiteExperience.ps1 -Credential $cred -Site https://contoso.sharepoint.com/sites/HR -Tenant contoso.sharepoint.com -Experience Classic
Switch site https://tenant.sharepoint.com/sites/HR to the Classic library/list
experience.
 
.EXAMPLE
.\Set-SharePointSiteExperience.ps1 -Credential $cred -Site https://contoso.sharepoint.com/sites/HR,https://contoso.sharepoint.com/sites/Engineering -Tenant contoso -Experience Classic
Switch sites https://contoso.sharepoint.com/sites/HR and
https://contoso.sharepoint.com/sites/Engineering to the Classic library/list
experience.
 
.EXAMPLE
.\Set-SharePointSiteExperience.ps1 -Credential $cred -AllSites -Tenant contoso -Experience Modern
Switch all sites in tenant contoso to Modern library/list experience.
 
.NOTES
2020-04-21 Updated for PowerShell Gallery.
2019-05-17 Initial release.
 
#>

[CmdletBinding(DefaultParametersetName = 'SiteInputA')]

Param (
    # Credential object
    [Parameter(Mandatory = $true)]
    [System.Management.Automation.PSCredential]$Credential,
    
    # Specify a site or a list of sites
    [Parameter(ParameterSetName = 'SiteInputA', HelpMessage = 'Input one or more sites, comma separated.')]
    [array]$Site,
        
    [Parameter(ParameterSetName = 'SiteInputB', HelpMessage = 'Input one or more sites in a text file.')]
    [String]$InputFile,
    
    [Parameter(ParameterSetName = 'SiteInputC', HelpMessage = 'Apply to all sites.')]
    [switch]$AllSites,
    [Parameter(ParameterSetName = 'SiteInputC')]
    [switch]$IncludeGroupSites,
    
    [parameter(ParameterSetName = 'SiteInputA')]
    [parameter(ParameterSetName = 'SiteInputB')]
    [parameter(ParameterSetName = 'SiteInputC')]
    [switch]$SkipValidation,
        
    # Specify a tenant, either as 'tenant.onmicrosoft.com' or just 'tenant'
    [Parameter(mandatory = $true)]
    [String]$Tenant,
        
    # Site experience
    [ValidateSet("Classic", "Modern","Auto")]
    [string]$Experience,
    
    # Misc params
    [switch]$WebAndSiteLevel,
    [switch]$ListLevel = $true,
    [string]$Logfile = (Get-Date -Format yyyy-MM-dd) + "_SharePointSiteExperience.txt"
    ) # End Parameters

# Preferences
$ErrorActionPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'

# Functions
function Write-Log([string[]]$Message, [string]$LogFile = $Script:LogFile, [switch]$ConsoleOutput, [ValidateSet("SUCCESS", "INFO", "WARN", "ERROR", "DEBUG")][string]$LogLevel)
{
    $Message = $Message + $Input
    If (!$LogLevel) { $LogLevel = "INFO" }
    switch ($LogLevel)
    {
        SUCCESS { $Color = "Green" }
        INFO { $Color = "White" }
        WARN { $Color = "Yellow" }
        ERROR { $Color = "Red" }
        DEBUG { $Color = "Gray" }
    }
    if ($Message -ne $null -and $Message.Length -gt 0)
    {
        $TimeStamp = [System.DateTime]::Now.ToString("yyyy-MM-dd HH:mm:ss")
        if ($LogFile -ne $null -and $LogFile -ne [System.String]::Empty)
        {
            Out-File -Append -FilePath $LogFile -InputObject "[$TimeStamp] [$LogLevel] $Message"
        }
        if ($ConsoleOutput -eq $true)
        {
            Write-Host "[$TimeStamp] [$LogLevel] :: $Message" -ForegroundColor $Color
        }
    }
}

function LoadSharePointLibraries
{
    Write-Host -Fore Yellow "Locating SharePoint Server Client Components installation..."
    If (Test-Path 'c:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll')
    {
        Write-Host -ForegroundColor Green "Found SharePoint Server Client Components installation."
        Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll"
        Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
        Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Taxonomy.dll"
        Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.UserProfiles.dll"
    }
    ElseIf ($filename = (Get-ChildItem 'C:\Program Files' -Recurse -ea silentlycontinue | where { $_.name -eq 'Microsoft.SharePoint.Client.DocumentManagement.dll' })[0])
    {
        $Directory = ($filename.DirectoryName)[0]
        Write-Host -ForegroundColor Green "Found SharePoint Server Client Components at $Directory."
        Add-Type -Path "$Directory\Microsoft.SharePoint.Client.dll"
        Add-Type -Path "$Directory\Microsoft.SharePoint.Client.Runtime.dll"
        Add-Type -Path "$Directory\Microsoft.SharePoint.Client.Taxonomy.dll"
        Add-Type -Path "$Directory\Microsoft.SharePoint.Client.UserProfiles.dll"
    }
    
    ElseIf (!(Test-Path 'C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll'))
    {
        Write-Host -ForegroundColor Yellow "This script requires the SharePoint Server Client Components. Attempting to download and install."
        wget 'https://download.microsoft.com/download/E/1/9/E1987F6C-4D0A-4918-AEFE-12105B59FF6A/sharepointclientcomponents_15-4711-1001_x64_en-us.msi' -OutFile ./SharePointClientComponents_15.msi
        wget 'https://download.microsoft.com/download/F/A/3/FA3B7088-624A-49A6-826E-5EF2CE9095DA/sharepointclientcomponents_16-4351-1000_x64_en-us.msi' -OutFile ./SharePointClientComponents_16.msi
        msiexec /i SharePointClientComponents_15.msi /qb
        msiexec /i SharePointClientComponents_16.msi /qb
        Sleep 60
        If (Test-Path 'c:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll')
        {
            Write-Host -ForegroundColor Green "Found SharePoint Server Client Components."
            Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll"
            Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
            Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Taxonomy.dll"
            Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.UserProfiles.dll"
        }
        Else
        {
            Write-Host -NoNewLine -ForegroundColor Red "Please download the SharePoint Server Client Components from "
            Write-Host -NoNewLine -ForegroundColor Yellow "https://download.microsoft.com/download/F/A/3/FA3B7088-624A-49A6-826E-5EF2CE9095DA/sharepointclientcomponents_16-4351-1000_x64_en-us.msi "
            Write-Host -ForegroundColor Red "and try again."
            Break
        }
    }
    
    If (!(Get-Module -ListAvailable "*online.sharepoint*"))
    {
        Write-Host -ForegroundColor Yellow "This script requires the SharePoint Online Management Shell. Attempting to download and install."
        wget 'https://download.microsoft.com/download/0/2/E/02E7E5BA-2190-44A8-B407-BC73CA0D6B87/SharePointOnlineManagementShell_6802-1200_x64_en-us.msi' -OutFile ./SharePointOnlineManagementShell.msi
        msiexec /i SharePointOnlineManagementShell.msi /qb
        Write-Host -ForegroundColor Yellow "Please close and reopen the Windows Azure PowerShell module and re-run this script."
    }
    If (!(Get-Module -ListAvailable SharePointPnPPowerShellOnline | ? { $_.Version -ge '3.4.1812.1' }))
    {
        Install-Module SharePointPnPPowerShellOnline -Force
    }
}

function ConnectToSPO
{
    If ($tenant -like "*.onmicrosoft.com") { $tenant = $tenant.split(".")[0] }
    $AdminURL = "https://$tenant-admin.sharepoint.com"
    Connect-SpoService -Credential $Credential -Url $AdminURL
    Import-Module SharePointPnPPowerShellOnline
}

LoadSharePointLibraries
ConnectToSPO

$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Credential.UserName, $Credential.Password)
switch ($PSBoundParameters.Keys)
{
    AllSites {
        switch ($IncludeGroupSites)
        {
            false {
                $Sites = (Get-SpoSite -Limit All | ? { $_.Url -notmatch "\-my\.sharepoint\.com" -and ($_.Template -ne "GROUP#0") }).Url
            }
            true {
                $Sites = (Get-SpoSite -Limit All | ? { $_.Url -notmatch "\-my\.sharepoint\.com" }).Url
            }
            default
            {
                $Sites = (Get-SpoSite -Limit All | ? { $_.Url -notmatch "\-my\.sharepoint\.com" -and ($_.Template -ne "GROUP#0") }).Url
            }
        }
    }
    
    Site {
        $Sites = @()
        If (!($SkipValidation))
        {
            foreach ($url in $Site)
            {
                try { $Sites += (Get-SpoSite -Identity $url -ea stop).Url }
                catch { "Unable to find site $($Site)" }
            }
        }
        Else { [array]$Sites = $Site }
    }
    
    InputFile {
        $SiteList = Get-Content $InputFile
        If (!($SkipValidation))
        {
            $Sites = @()
            {
                foreach ($url in $SiteList)
                {
                    try { $Sites += (Get-SpoSite -Identity $url -ea stop).Url }
                    catch { "Unable to find site $($Site)" }
                }
            }
            Else { [array]$Sites = $Site }
        }
    }
}

[int]$SitesTotal = $Sites.Count
[int]$s = 1
foreach ($site in $Sites)
{
    Write-Log -LogFile $Logfile -Message "Updating site $($Site) to $($Experience) Experience." -LogLevel INFO
    Write-Progress -Activity "Updating Sites to $($Experience) Experience" -PercentComplete (($s / $SitesTotal) * 100) -Status "$($SitesTotal - $s) sites remaining" -Id 1
    Write-Progress -Activity "Site: $($Site)" -Id 2 -ParentId 1
    
    $cmd = "Connect-PnpOnline -Url $($Site) -credentials `$Credential"
    Invoke-Expression $cmd
    If ($ListLevel)
    {
        $lists = Get-PnPList -Includes ListExperienceOptions;
        $ListsTotal = $lists.Count
        $l = 1
        foreach ($list in $lists)
        {
            Write-Progress -Activity "Updating list $($list.Title)" -PercentComplete (($l / $ListsTotal) * 100) -Id 3 -ParentId 2
            switch ($Experience)
            {
                Classic {
                    $list.ListExperienceOptions = 2;
                }
                Modern {
                    $list.ListExperienceOptions = 1;
                }
                Auto {
                    $list.ListExperienceOptions = auto;
                }
            }
            
            try
            {
                $list.Update(); Invoke-PnPQuery
                Write-Log -LogFile $Logfile -LogLevel SUCCESS -Message "Updated $($Site)$($list.Title) to $($Experience)."
            }
            catch
            {
                Write-Log -LogFile $Logfile -LogLevel ERROR -Message "Failed to update $($Site)$($list.Title) to $($Experience) - Not supported for this list/library type."
            }
            $l++
        }
    }
    # Updating Web and Collection settings
    # These settings are documented, but don't appear to work
    If ($WebAndSiteLevel)
    {
        switch ($Experience)
        {
            Classic {
                Enable-PnPFeature -Identity E3540C7D-6BEA-403C-A224-1A12EAFEE4C4 -Scope Site
                Enable-PnpFeature -Identity 52E14B6F-B1BB-4969-B89B-C4FAA56745EF -Scope Web
            }
            Modern {
                Disable-PnPFeature -Identity E3540C7D-6BEA-403C-A224-1A12EAFEE4C4 -Scope Site
                Disable-PnpFeature -Identity 52E14B6F-B1BB-4969-B89B-C4FAA56745EF -Scope Web
            }
        }
    }
    $s++
}