Grant Permissions for Everyone in SharePoint Online with PowerShell

In some deployment scenarios we must grant permissions for Everyone or Everyone except external users. Doing so in the browser interface is easy, but in PowerShell it is somewhat difficult, because we need the claim. The claim for Everyone is “c:0(.s|true”. So with PowerShell we can use this call (we are using the PowerShell PnP extensions to make our life easier) to add Everyone to the Visitors group of a site:


$group = Get-PnPGroup -AssociatedVisitorGroup
Add-PnPUserToGroup -LoginName "c:0(.s|true" -Identity $group

For doing the same for Everyone except external users we need to know the “syntax” of the claim that is needed. The claim for Everyone except external users looks like “c:0-.f|rolemanager|spo-grid-all-users/{tenant-id}”. So, to set the permissions for this group we need to assemble the token. But with PowerShell PnP that is not that problem. These simple three lines of code can do the job:


$realm = Get-PnPAuthenticationRealm
$loginName = "c:0-.f|rolemanager|spo-grid-all-users/$realm"
$group = Get-PnPGroup -AssociatedVisitorGroup
Add-PnPUserToGroup -LoginName $loginName -Identity $group

And that is the result, after running these two scripts:

Not so difficult, but it might be necessary that an administrator activates the ability to use these claims. See this article for more information.

 

Advertisements

Customize SharePoint Forms

Often in SharePoint projects we hear the requirement that the form of a list or library should be modified, depending on the values of the current item. This could be achieved with some JavaScript, but what, when this should also happen in subwebs. Well, this could also be done with JavaScript and when this customization is added in a proper way, it could be very easy. First, the following only works with the classic UI of SharePoint.

For this example, we use a very simple SharePoint list that was created using PowerShell, because only in this case we have full control over the internal name of a field and its Id.

The requirement will be that the field “Status Comment” should only be shown, when the value of the “Status Field” is finished. In all other cases, this field should be hidden for the user. We keep this example simple, but in real world scenarios it could be very complex. Our solution must modify all three forms of the list, the one for new item, the one for editing items and the one to display an item. An example for some JavaScript to modify the edit form could look like this:


function pbPageModificationFunc() {
    var statusValue = $("#StatusField_8bd36554-9b41-49be-9c96-c2e25743c53a_\\$DropDownChoice option:selected").text();

    if (statusValue != "finished") {
        $("#StatusComment").parent().parent().hide();
    }

    setOnChangeForStatusField();
}

function setOnChangeForStatusField() {
    $("#StatusField_8bd36554-9b41-49be-9c96-c2e25743c53a_\\$DropDownChoice").on("change", function () { showStatusCommentField(); });
}

function showStatusCommentField() {
    var statusValue = $("#StatusField_8bd36554-9b41-49be-9c96-c2e25743c53a_\\$DropDownChoice option:selected").text();

    if (statusValue != "finished") {
        $("#StatusComment").parent().parent().hide();
    } else {
        $("#StatusComment").parent().parent().show();
    }
}

The main problem in the solution is, how to add this script to the form. This could be done using a Content Editor Webpart, but in this case we must manually add the webpart, when a similar list is added in any subweb. To automate this task, we use another JavaScript that is used as a loader.


// create a mapping for the form and the modifier file
var mapping =
{
    values:
    [
    	{ "/lists/statuslist/newform.aspx": "/scripts/StatusListNewForm.js" },
    	{ "/lists/statuslist/dispform.aspx": "/scripts/StatusListDispForm.js" },
        { "/lists/statuslist/editform.aspx": "/scripts/StatusListEditForm.js" }
    ]
};

$(document).ready(function () {
    var scriptFile = "";

    for (var i = 0; i < mapping.values.length; i++) {
        var obj = mapping.values[i];

        for (var key in obj) {
            var value = obj[key].toString();

            if (window.location.href.toLowerCase().indexOf(key) != -1) {
                scriptFile = _spPageContextInfo.siteAbsoluteUrl + value;

                break;
            }
        }
    }

    SP.SOD.registerSod("formModifier.js", scriptFile);

    SP.SOD.executeFunc('formModifier.js', null, function () {
        pbPageModificationFunc();
    });
});

This script has a mapping table that contains a fragment of the url for the page and the JavaScript file that should be added, when the user opens the page in the browser. This loader script is added by a JavaScript link to the whole site collection, so it runs for each page that a user opens. When the additional script for the current page was loaded, a function (pbPageModificationFunc) is called to modify the current page.


# the script is assuming that a connection to a SharePoint Online site is already established

Write-Host -ForegroundColor Magenta "Upload scripts"

Write-Host "Create scripts library"

$l = New-PnPList -Title "Scripts" -Template DocumentLibrary -Url "scripts" -OnQuickLaunch:$false

Write-Host "Disable modern ui for scripts library"

$list = Get-PnPList -Identity "Scripts" -Includes "ListExperienceOptions"
(Get-PnPContext).Load($list)
Invoke-PnPQuery

$list.ListExperienceOptions = "ClassicExperience"
$list.Update()
Invoke-PnPQuery

Write-Host "Upload the script files"

Write-Host "- FormModifierLoader.js"

$f = Add-PnPFile -Path .\Scripts\FormModifierLoader.js -Folder "scripts"

Write-Host "- StatusListNewForm.js"

$f = Add-PnPFile -Path .\Scripts\StatusListNewForm.js -Folder "scripts"

Write-Host "- StatusListDispForm.js"

$f = Add-PnPFile -Path .\Scripts\StatusListDispForm.js -Folder "scripts"

Write-Host "- StatusListEditForm.js"

$f = Add-PnPFile -Path .\Scripts\StatusListEditForm.js -Folder "scripts"

Write-Host "Register script files"

Write-Host "- jQuery"

$jsLink = Get-PnPJavaScriptLink -Name "jQuery" -Scope Site

if ($jsLink -ne $null)
{
	Remove-PnPJavaScriptLink -Identity "jQuery" -Scope Site -Force
}

Add-PnPJavaScriptLink -Name "jQuery" -Url "https://code.jquery.com/jquery.min.js" -Sequence 10 -Scope Site

Write-Host "- FormModifierLoader.js"

$site = Get-PnPSite
$fileUrl = $site.Url + "/Scripts/FormModifierLoader.js"

$jsLink = Get-PnPJavaScriptLink -Name "FormModifierLoader" -Scope Site

if ($jsLink -ne $null)
{
	Remove-PnPJavaScriptLink -Identity "FormModifierLoader" -Scope Site -Force
}

Write-Host $fileUrl

Add-PnPJavaScriptLink -Name "FormModifierLoader" -Url $fileUrl -Sequence 15 -Scope Site

Write-Host "Done."

To organize the files, we store the JavaScript files in a separate library called “Scripts”. And that’s all. After all scripts are available in the site collection and the loader script is registered, the desired functionality is available for the user.

The whole demo project is available in a GitHub project. To deploy the solution, a SharePoint team site with the classic UI must be created and the PowerShell PnP extensions must be available.

 

 

Caml query to find all items in a list

To get all list items in a list (or library) in SharePoint Online with the client side object model, a simple caml query is needed. I usually use this one:


<View>
  <Query>
    <Where>
	  <Gt>
	    <FieldRef Name='ID' />
		<Value Type='Number'>0</Value>
      </Gt>
	</Where>
  </Query>
</View>

Simple solution to remove the gear icon in SharePoint Online

The gear icon in SharePoint Online is the key to a lot of functionality in SharePoint Online, like Site Content or the Site Settings. But sometimes the owner of a site wants to have this disabled. Because SharePoint is a web application, we can do a lot using JavaScript.

So the script to remove the gear icon can look like this:


// make the gear icon on the top right invisible for users, who do not have
// manage permissions for the web.
// the solutions assumes that jQuery is available

$(document).ready(function() {
	if (_spPageContextInfo.hasManageWebPermissions == false) {
		setInterval(function() {
			$("#O365_MainLink_Settings.o365cs-nav-item.o365cs-nav-button.o365cs-topnavText.ms-bgc-tdr-h.o365button").hide();
		}, 500);
	}
});

In the script we check, whether the user has the permission to manage the web. When not, the gear icon is removed.

To make this script available in all pages within a site collection we just need some PowerShell scripting that makes use of the PowerShell PnP extensions. This example will store the script file in a library called "Scripts" and is assuming that this library already exists.


Write-Host "Upload RemoveGearIcon.js"

$f = Add-PnPFile -Path .\RemoveGearIcon.js -Folder "scripts"

$site = Get-PnPSite
$fileUrl = $site.Url + "/Scripts/RemoveGearIcon.js"

Write-Host "Add JavaScript Link"

$jsLink = Get-PnPJavaScriptLink -Name "RemoveGearIcon" -Scope Site

if ($jsLink -ne $null)
{
	Remove-PnPJavaScriptLink -Identity "RemoveGearIcon" -Scope Site -Force
}

$link = Add-PnPJavaScriptLink -Name "RemoveGearIcon" -Url $fileUrl -Sequence 200 -Scope Site

That’s all. The result will look like this:

Which template was used for my SharePoint Online site?

When in the web browser, we are able to get information about the used template during site creation. Just open the console in the developer tools and enter

_spPageContextInfo.webTemplateConfiguration

This will show up the web template and the used configuration.

Create SharePoint Content Type with Document Template

With the PnP PowerShell extensions for SharePoint Online it’s very easy to create a new content type by using the Add-PnPContentType cmdlet. But sometimes it is necessary to assign a new document template to the content type (or an existing one). This could be done using Client-site Object Model within the PowerShell Script as shown in the following script.


$filePath = ".\Document Template\DocumentTemplate.docx"
$filename = "DocumentTemplate.docx"

$serverRelativeSiteUrl = "/sites/my-demo"
$ctName = "Any new Document"

# create the content type
$ct = Add-PnPContentType -Name $ctName -ContentTypeId 0x0101006604da7f262243448cb56226f4f30c79 -Group "Tester" -Description "No description available."

# upload the document template to the corresponding folder of the content type (site relative url)
$f = Add-PnPFile -Path $filePath -Folder "/_cts/$ctName"

# get the content type object
$ct = Get-PnPContentType -Identity $ctName 

# set the document template in the content type to the uploaded file and update in the content database
$ct.DocumentTemplate = $filename
$ct.Update($true)
(Get-PnPContext).Load($ct)
Invoke-PnPQuery

# that's it

The result in the web view of the settings of the content type will look like this:

Keep it simple .

Reindex Complete SharePoint Site Collection

In SharePoint Online we do not have the option, to trigger reindexing the complete site collection. The reindex request must be send for each web in the site collection. The following script could be used, when the whole site collection needs to be reindexed.

#
# Parameters
# ==========
# 
# Url : root url of the site collection
# Credentials : username and password to connect with
#

param (
    [string]$Url,
    $Credentials
)

function Iterate-WebCollection
{
   param (
       $WebCollection
   )

    foreach ($web in $WebCollection)
    {
        $webUrl = $web.Url

        Write-Host -NoNewline $webUrl " ... "

        Connect-PnPOnline -Url $webUrl -Credentials $Credentials

        Request-PnPReIndexWeb

        Write-Host -ForegroundColor Green "Done."

        $CurrentWeb = Get-PnPWeb -Includes Webs

        Iterate-WebCollection -WebCollection $CurrentWeb.Webs

        Connect-PnPOnline -Url $webUrl -Credentials $Credentials
    }
}

if ($Credentials -eq $null)
{
    $Credentials = Get-Credential
}

Connect-PnPOnline -Url $Url -Credentials $Credentials

$web = Get-PnPWeb -Includes Webs
$webUrl = $web.Url

Write-Host -NoNewline $Url " ... "

Request-PnPReIndexWeb

Write-Host -ForegroundColor Green "Done."

Iterate-WebCollection -WebCollection $web.Webs