PowerShell to create SharePoint Sites

Very frequently I find myself creating SharePoint sites for clients, normally during migrations to the cloud during holiday periods, this is along drawn out task and can often lead to steps being missed.

Problem Solved!

You can use the below PowerShell script to create SharePoint Sites automatically along with disabling Sharing Links etc to ensure no GDPR data leaks.

clear
#------------------------------------------------
#--------------V1 initial release----------------
#----------------DR 15/10/2021-------------------
#------------------------------------------------
#------------------Infomation--------------------
#You will be require to login three time for this script to function 
#as you need to login to different parts of Office 365, Azure and 
#SharePoint, logins are Interactive to allow 2FA accounts to work also

#Once the sites has been created, if you are using Azure AD Connect and
#set $ConnectToAD = $true below users will need adding into the correct
#groups to get the correct permissions

#Disabling sharing links etc will ensure no data is able to be shared with
#users who do not alread have access to site files

#Giving users Owner permissions will enabled them to be able to create
#sharing links outside the the site i.e. people who do not already have access
#------------------------------------------------

#--------------Permissions reference-------------
#Visitors (Read Access)
#Members (Contirubte/Write)
#Owners (Full Control of the SharePoint site)
#------------------------------------------------

#-------------------Change Log-------------------
#18-10-21 - Updated the section that adds AD User groups to SharePoint to use c:0t.c|tenant| as the start of the entry point for group names.
#19-10-21 - Set-GPPermission now removed Authenticated Users from having the GPO Applied, the GPOS are now only applied to the AD SharePoint groups

#---------------------To Do----------------------
#Find a way for the GPOs to be auto populated with the library names and IDs

#------------------------------------------------
#User configurable variables
$TenantName = "" # Your Azure tenant name (the bit before -admin.sharepoint.com)
$TenantFriendlyName = "" # A friendly name for your tenant, Company name, School name etc
$SiteName = "" # The name of the site to be created
$OwnerEmail = "" # The email of the user who is to be the new site Owner
$StorageQuota = "26214400" # Storage Quota for the site, 26214400 is 25Tb the default
$ConnectToAD = $true # Are you using Azure AD connect and want to connect AD Groups to SharePoint if false then perms will need adding manually
$ADGroupPath = "" # Use an LDAP Path here thats sync to Office 365 to create the AD Groups
#------------------------------------------------

#------------------------------------------------
#Do not alter these unless you know what you are doing!
$docLib = 'Documents'
$TenantAdminURL = "https://" + $TenantName + "-admin.sharepoint.com"
$TenantClientURL = "https://" + $TenantName + ".sharepoint.com"
$SiteURL = $TenantClientURL + "/sites/" + $SiteName
$ADOwnersGroupName = "SharePoint - " + $TenantFriendlyName + " - " + $SiteName + " Owners"
$ADMembersGroupName = "SharePoint - " + $TenantFriendlyName + " - " + $SiteName + " Members"
$ADVisitorsGroupName = "SharePoint - " + $TenantFriendlyName + " - " + $SiteName + " Visitors"
$GPOName = "SharePoint - " + $TenantFriendlyName + " - " + $SiteName
$SPOwnersGroupName = $SiteName + " Owners"
$SPMembersGroupName = $SiteName + " Members"
$SPVisitorsGroupName = $SiteName + " Visitors"
#------------------------------------------------

#------------------------------------------------WORKING
#Check we are running as Admin and elevate if not
write-host "Info: Checking we are elevated" -foregroundcolor green
$ver = $host | select version
if ($ver.Version.Major -gt 1)  {$Host.Runspace.ThreadOptions = "ReuseThread"}

# Verify that user running script is an administrator
$IsAdmin=[Security.Principal.WindowsIdentity]::GetCurrent()
If ((New-Object Security.Principal.WindowsPrincipal $IsAdmin).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) -eq $FALSE)
{
	"`nERROR: You are NOT a local administrator.  Run this script after elevating to Administrator."
	exit
}
#------------------------------------------------

#------------------------------------------------WORKING
#Uninstall old SharePointPnPPowerShellOnline if it exists
Write-Host "Uninstall old SharePointPnPPowerShellOnline if it exists" -foregroundcolor green
If (Get-InstalledModule Microsoft.Online.SharePoint.PowerShell) {
    Write-Host "SharePointPnPPowerShellOnline is installed, uninstalling." -foregroundcolor green
    Uninstall-Module -Name SharePointPnPPowerShellOnline -AllVersions -Force
}

#Install dependancies
write-host "Info: Install dependancies" -foregroundcolor green
If (-not(Get-InstalledModule Microsoft.Online.SharePoint.PowerShell)) {
    Write-Host "Module Microsoft.Online.SharePoint.PowerShell does not exist, installing." -foregroundcolor green
    Install-Module -Name Microsoft.Online.SharePoint.PowerShell
	} else {
	write-host "Info: Module Microsoft.Online.SharePoint.PowerShell already Installed" -foregroundcolor green
}

If (-not(Get-InstalledModule PnP.PowerShell)) {
    Write-Host "Module PnP.PowerShell does not exist, installing." -foregroundcolor green
    Install-Module -Name PnP.PowerShell
	} else {
	write-host "Info: Module PnP.PowerShell already Installed" -foregroundcolor green
}

If (-not(Get-InstalledModule AzureAD)) {
    Write-Host "Module AzureAD does not exist, installing." -foregroundcolor green
    Install-Module -Name AzureAD
	} else {
	write-host "Info: Module AzureAD already Installed" -foregroundcolor green
}
#------------------------------------------------

#------------------------------------------------WORKING
#Get latest updates for modules
write-host "Info: Updating modules to latest versions" -foregroundcolor green
Update-Module -Name Microsoft.Online.SharePoint.PowerShell
Update-Module -Name PnP.PowerShell
Update-Module -Name AzureAD
#------------------------------------------------

#------------------------------------------------WORKING
#Connect to to SPOService
try{
	write-host "Info: connecting to SPOService" -foregroundcolor green
    Connect-SPOService -Url $TenantAdminURL
    write-host "Info: Connected succesfully to SPOService" -foregroundcolor green
}
catch{
    write-host "Error: Could not connect to SPOService" -foregroundcolor red
    exit
}
#------------------------------------------------

#------------------------------------------------WORKING
if ($ConnectToAD -eq $true) {
	#Create the AD Groups
	write-host "Info: Create AD Groups" -foregroundcolor green
	New-ADGroup -Name $ADOwnersGroupName -SamAccountName $ADOwnersGroupName -GroupCategory Security -GroupScope DomainLocal -DisplayName $ADOwnersGroupName -Path $ADGroupPath -Description "Membership of this group gives Owner (Full Control) access to the root of $SiteName"
	New-ADGroup -Name $ADMembersGroupName -SamAccountName $ADMembersGroupName -GroupCategory Security -GroupScope DomainLocal -DisplayName $ADMembersGroupName -Path $ADGroupPath -Description "Membership of this group gives Member (Read and Write) access to the root of $SiteName"
	New-ADGroup -Name $ADVisitorsGroupName -SamAccountName $ADVisitorsGroupName -GroupCategory Security -GroupScope DomainLocal -DisplayName $ADVisitorsGroupName -Path $ADGroupPath -Description "Membership of this group gives Visitor (Read) access to the root of $SiteName"
	
    #Create Group Policies
	write-host "Info: Create GPO" -foregroundcolor green
    New-GPO -Name $GPOName
	#Remove Authenticated Users from having the GPO Applied
	Set-GPPermission -Name $GPOName -TargetName "Authenticated Users" -TargetType Group -PermissionLevel GpoRead -Replace
	#Add the new AD Groups to have the GPO Applied
	Set-GPPermission -Name $GPOName -TargetName $ADOwnersGroupName -TargetType Group -PermissionLevel GpoApply
	Set-GPPermission -Name $GPOName -TargetName $ADMembersGroupName -TargetType Group -PermissionLevel GpoApply
	Set-GPPermission -Name $GPOName -TargetName $ADVisitorsGroupName -TargetType Group -PermissionLevel GpoApply

	
	#Run a Delta Sync to Office 365
	write-host "Info: Run AD Delta Sync" -foregroundcolor green
	Start-ADSyncSyncCycle -PolicyType Delta
	
    #Wait for 3 minutes to allow 365 to register the sync
    $Seconds = 180
    $EndTime = [datetime]::UtcNow.AddSeconds($Seconds)
    while (($TimeRemaining = ($EndTime - [datetime]::UtcNow)) -gt 0) {
		Write-Progress -Activity 'Watiting for...' -SecondsRemaining $TimeRemaining.TotalSeconds
		Start-Sleep 1
	}
}
#------------------------------------------------

#------------------------------------------------WORKING
#verify if site already exists in SharePoint Online
write-host "Info: Checking if site already exists" -foregroundcolor green
$siteExists = get-SPOSite -Limit ALL | where{$_.url -eq $SiteURL}

#verify if site already exists in the recycle bin
write-host "Info: Checking if site already exists in the recycle bin" -foregroundcolor green
$siteExistsInRecycleBin = get-SPODeletedSite -Limit ALL | where{$_.url -eq $SiteURL}

#create site if it doesn't exists
if (($siteExists -eq $null) -and ($siteExistsInRecycleBin -eq $null)) {
    write-host "Info: Creating $($SiteName)" -foregroundcolor green
    #Create a new site with the Doc Centre template BDR#0
    New-SPOSite -Url $SiteURL -Owner $OwnerEmail -StorageQuota $StorageQuota -CompatibilityLevel 15 -LocaleID 1033 -Template "BDR#0" -TimeZoneId 2 -Title $SiteName
	write-host "Info: Site $($SiteName) Created" -foregroundcolor green
}
elseif ($siteExists -eq $true){
    write-host "Error: $($SiteURL) already exists" -foregroundcolor red
    exit
}
else{
    write-host "Error: $($SiteURL) still exists" -foregroundcolor red
    exit
}
#------------------------------------------------

#------------------------------------------------WORKING
#Wait for 3 minutes for Office 365 to acknowledge the new site
write-host "Waiting for 3 minutes for Office 365 to acknowledge the new site" -foregroundcolor green
$Seconds = 180
$EndTime = [datetime]::UtcNow.AddSeconds($Seconds)
while (($TimeRemaining = ($EndTime - [datetime]::UtcNow)) -gt 0) {
	Write-Progress -Activity 'Watiting for...' -SecondsRemaining $TimeRemaining.TotalSeconds
	Start-Sleep 1
}
#------------------------------------------------

#------------------------------------------------WORKING
#Connect to PnPOnline now the site is created
try{
	write-host "Info: Connecting to PnPOnline" -foregroundcolor green
    Connect-PnPOnline -Url $SiteURL -Interactive
    write-host "Info: Connected succesfully to PnPOnline" -foregroundcolor green
}
catch{
    write-host "Error: Could not connect to PnPOnline" -foregroundcolor red
    exit
}
#------------------------------------------------

#------------------------------------------------WORKING
#Disable check out on the new site
write-host "Info: Disabling check out on the new site" -foregroundcolor green
#Function to Disable Checkout Option for all libraries in a site
Function Disable-PnPRequireCheckout
{
    param
    (
	[parameter(Mandatory = $true, ValueFromPipeline = $True)]$Web
    )
	
    Try {
        Write-host "Processing Web:"$Web.URL -foregroundcolor green
		
        #Array to exclude system libraries
        $SystemLibraries = @("Form Templates", "Pages", "Preservation Hold Library","Site Assets", "Site Pages", "Images",
		"Site Collection Documents", "Site Collection Images","Style Library","Drop Off Library")
		
        $Libraries = Get-PnPList -Includes BaseType, Hidden, EnableVersioning -ErrorAction Stop
        #Get All document libraries
        $DocumentLibraries = $Libraries | Where {$_.BaseType -eq "DocumentLibrary" -and $_.Hidden -eq $False -and $_.Title -notin $SystemLibraries}
		
        #Disable Checkout
        ForEach($Library in $DocumentLibraries)
        {
            If($Library.ForceCheckout)
            {
                #Powershell to disable checkout for the library
                Set-PnPList -Identity $Library -ForceCheckout $false
                Write-host "`tRequire Check-out disabled on '$($Library.Title)'" -f Green 
			}
            Else
            {
                Write-host "`tRequire Check Out is already set to 'No' at '$($Library.Title)'" -f Yellow 
			}
		}
	}
    Catch {
        Write-host "`tError:" $_.Exception.Message -foregroundcolor Red 
	}
}
Get-PnPWeb | Disable-PnPRequireCheckout  
Get-PnPSubWeb -Recurse | ForEach-Object { Disable-PnPRequireCheckout $_ }
#------------------------------------------------

#------------------------------------------------WORKING
#Function to disable access request on SharePoint Online
write-host "Info: Disabling access request on the new site" -foregroundcolor green
Function Disable-PnPAccessRequest
{
    [cmdletbinding()]
    Param(
	[parameter(Mandatory = $true, ValueFromPipeline = $True)] $Web
    )
    Try {
        Write-host "Disabling Access Request on:"$web.Url -foregroundcolor Yellow 
        If($Web.HasUniqueRoleAssignments)
        {
            #Disable Access Request          
            $Web.RequestAccessEmail = [string]::Empty
            $Web.SetUseAccessRequestDefaultAndUpdate($False)
            $Web.Update()
            Invoke-PnPQuery
            Write-host "`tAccess Request has been Disabled!"$web.Url -foregroundcolor Green 
		}
        else
        {
            Write-host "`tWeb inherits permissions from the parent!"$web.Url -foregroundcolor Yellow 
		}
	}
    Catch {
        write-host "`tError Disabling Access Request: $($_.Exception.Message)" -foregroundcolor Red
	}
}
#Call the Function for Root Web and all Subwebs
Get-PnPWeb -Includes HasUniqueRoleAssignments | Disable-PnPAccessRequest
Get-PnPSubWeb -Recurse -Includes HasUniqueRoleAssignments | ForEach-Object { Disable-PnPAccessRequest $_ }
#------------------------------------------------

#------------------------------------------------WORKING
#Disable Members sharing files folders or the site
write-host "Info: Disable Members sharing files folders or the site" -foregroundcolor green
function DisableMemberSharing($siteUrl ) {
    write-host "Info: Disabling Members sharing files folders or the site" -foregroundcolor green
    $web = Get-PnPWeb -Includes MembersCanShare, AssociatedMemberGroup.AllowMembersEditMembership
    $web.MembersCanShare=$false
    $web.AssociatedMemberGroup.AllowMembersEditMembership=$false
    $web.AssociatedMemberGroup.Update()
    $web.RequestAccessEmail = $null
    $web.Update()
    $web.Context.ExecuteQuery()
}
write-host "Info: Disabling sharing for none owners" -foregroundcolor green
Set-SPOSite -Identity $SiteURL -DisableSharingForNonOwners
Set-SPOSite -Identity $SiteURL -SharingCapability Disabled -DefaultLinkToExistingAccess $true -DisableCompanyWideSharingLinks Disabled
#------------------------------------------------

#------------------------------------------------WORKING
#Connect to PNPOnline and AzureAD
write-host "Info: Connect to PnPOnline ManagementShell" -foregroundcolor green
try{
	write-host "Info: Connecting to PnPManagementShell" -foregroundcolor green
    Connect-PnPOnline $SiteURL -PnPManagementShell
    write-host "Info: Connected succesfully to PnPManagementShell" -foregroundcolor green
}
catch{
    write-host "Error: Could not connect to PnPManagementShell" -foregroundcolor red
    exit
}

write-host "Info: Connect to AzureAD" -foregroundcolor green
try{
	write-host "Info: Connecting to AzureAD" -foregroundcolor green
    Connect-AzureAD
    write-host "Info: Connected succesfully to AzureAD" -foregroundcolor green
}
catch{
    write-host "Error: Could not connect to AzureAD" -foregroundcolor red
    exit
}

#------------------------------------------------WORKING
#Add the AD Groups to SharePoint
write-host "Info: Add the AD Groups to SharePoint" -foregroundcolor green

if ($ConnectToAD -eq $true) {
	
	# Get the SharePoint site
	$SPOSite = Get-SPOSite $SiteURL
	
	$ADOwnersGroupID = (Get-AzureADGroup -SearchString $ADOwnersGroupName).ObjectId
	$OwnersLoginName = "c:0t.c|tenant|" + $ADOwnersGroupID
	write-host "Info: Adding AD Group $ADOwnersGroupName with Login ID $OwnersLoginName to Owners" -foregroundcolor green
	Add-SPOUser -Site $SPOSite -LoginName $OwnersLoginName -Group $SPOwnersGroupName
	
	$ADMembersGroupID = (Get-AzureADGroup -SearchString $ADMembersGroupName).ObjectId
	$MembersLoginName = "c:0t.c|tenant|" + $ADMembersGroupID
	write-host "Info: Adding AD Group $ADMembersGroupName with Login ID $MembersLoginName to Members" -foregroundcolor green
	Add-SPOUser -Site $SPOSite -LoginName $MembersLoginName -Group $SPMembersGroupName
	
	$ADVisitorsGroupID = (Get-AzureADGroup -SearchString $ADVisitorsGroupName).ObjectId
	$VisitorLoginName = "c:0t.c|tenant|" + $ADVisitorsGroupID
	write-host "Info: Adding AD Group $ADVisitorsGroupName with Login ID $VisitorLoginName to Visitors" -foregroundcolor green
	Add-SPOUser -Site $SPOSite -LoginName $VisitorLoginName -Group $SPVisitorsGroupName
	
}
#------------------------------------------------

#------------------------------------------------WORKING
#Get the library IDs
write-host "Info: Get Azure Tenant ID" -foregroundcolor green
$tenantId = (Get-AzureADTenantDetail).objectId
write-host "Info: Tenant ID: $tenantId" -foregroundcolor green

$PnPSite = Get-PnPSite -Includes Id | Select Id
$PnPWeb = Get-PnPWeb | Select Id
$PnPList = Get-PnPList 'Documents' | Select Id

# Convert Tenant ID
$tenantId = $tenantId -replace '-','%2D'

# Convert Site ID
$PnPSite = Get-PnPSite -Includes Id | select id
$PnPSite = $PnPSite.Id -replace '-','%2D'
$PnPSite = '%7B' + $PnPSite + '%7D'

# Convert Web ID
$PnPWeb = Get-PnPWeb -Includes Id | select id
$PnPWeb = $PnPWeb.Id -replace '-','%2D'
$PnPWeb = '%7B' + $PnPWeb + '%7D'

# Convert List ID
$PnPList = Get-PnPList $docLib -Includes Id | select id
$PnPList = $PnPList.Id -replace '-','%2D'
$PnPList = '%7B' + $PnPList + '%7D'
$PnPList = $PnPList.toUpper()

$FULLURL = 'tenantId=' + $tenantId + '&siteId=' + $PnPSite + '&webId=' + $PnPWeb + '&listId=' + $PnPList + '&webUrl=https%3A%2F%2F' + $TenantName + '%2Esharepoint%2Ecom%2Fsites%2F' + $siteName + '&version=1'

# Output the FULL URL To Copy and Paste
Write-Output 'Library ID: ' $FULLURL
Write-Host "You will now need to manually add this library ID to the GPO - $GPOName" -foregroundcolor magenta
#------------------------------------------------

Found priceless insights in this blog? Support the author’s creativity – buy them a coffee!

Leave a Reply

Your email address will not be published. Required fields are marked *