Delete random blank Excel cells and shift remaining cells left to fill the gaps

Photo by Andrea Albanese from Pexels

I love learning new things. Today I was using a CSV export of data in Excel and was just using Excel to reassemble the data. At one point it resembled something like:

This is, of course, after some manipulation. Basically I removed the parts of a URL column expanded by delimiter (“/”) I didn’t want. This left awkward gaps and all I wanted was to quickly delete the gaps and shift contents of remaining cells to the left to sort of “reconstruct” each row without gaps. Since they didn’t line up in neat columns, I needed a different method.

I found a great article that helped. To summarize the steps:

  1. Select the range for which you’ll delete blank cells and shift data left.
  2. Press Ctrl+G.
  3. Click Special… (lower left of dialog)
  4. Choose the Blanks radio button
  5. Click OK.
  6. All blank cells in the selected range remain highlighted. Now right-click any of the selected blank cells.
  7. Choose Delete.
  8. Select Shift cells left.
  9. Click OK.

Here’s an animated GIF showing the process on my example data:

Click to enlarge

Export a report of all SharePoint lists, libraries, discussion boards, calendars, and more from all site collections with PowerShell

Photo by Tiger Lily from Pexels

I wanted to get an idea of how many people were using discussion boards in my SharePoint Server environment and in what sites. I modified a script I found to create the sort of inventory list I needed and ended with a script that:

  • Provides a list within PowerShell of each URL, site name, and list name found matching a specific list type.
  • Provides a total count and save location confirmation at the end.
  • Exports a CSV file of the details to a specified location.

Find the Template ID for the list you’re searching for

List template ID options are as follows, taken from this Docs article. You’ll need the Template ID of the type of list you wish to search in your SharePoint environment.

List template typeTemplate IDBase typeDescription
Custom List1000A basic list that can be adapted for multiple purposes.
Document Library1011Contains a list of documents and other files.
Survey1024Fields (2) on a survey list represent questions that are asked of survey participants. Items in a list represent a set of responses to a survey.
Links1030Contains a list of hyperlinks and their descriptions.
Announcements1040Contains a set of simple announcements.
Contacts1050Contains a list of contacts used for tracking people in a site (2).
Calendar1060Contains a list of single and recurring events. An events list has special views for displaying events on a calendar.
Tasks1070Contains a list of items that represent finished and pending work items.
Discussion Board1080Contains discussions entries and their replies.
Picture Library1091Contains a library adapted for storing and viewing digital pictures.
DataSources1101Contains data connection description files.
Form Library1151Contains XML documents. An XML form library can also contain templates for displaying and editing XML files through forms, as well as rules for specifying how XML data is converted to and from list items.
No Code Workflows1171Contains additional workflow definitions that describe new processes that can be used in lists. These workflow definitions do not contain advanced code-based extensions.
Custom Workflow Process1180Contains a list used to support custom workflow process actions.
Wiki Page Library1191Contains a set of editable Web pages.
CustomGrid1200Contains a set of list items with a grid-editing view.
No Code Public Workflows1221A gallery for storing workflow definitions that do not contain advanced code-based extensions.
Workflow History1400Contains a set of history items for instances of workflows.
Project Tasks1500Contains a list of tasks with specialized views of task data in the form of Gantt chart.
Public Workflows External List6000An external list for viewing the data of an external content type.
Issues Tracking11005Contains a list of items to track issues.
Table of list template IDs taken from https://docs.microsoft.com/en-us/openspecs/sharepoint_protocols/ms-wssts/8bf797af-288c-4a1d-a14b-cf5394e636cf.

Modify the PowerShell script to suit your needs

Once you have the list template ID you wish to query from the table above, modify the PowerShell script found below as follows:

  1. Replace the $ListTemplateId variable value with your desired template ID.
  2. Replace the -WebApplication parameter’s URL value with your own SharePoint web app address.
  3. Replace the $saveCSVLocation value inside the double-quotes with the path to where you’d like the export to be written.

Get all SharePoint list types in one report

Want it all? You could modify the script by replacing -eq $ListTemplateId on line 15 with -gt 0. Then replace line 21 with:

@{Site=$web.Title;ListName=$list.Title;Type=$list.BaseTemplate;URL=($web.Url + "/" + $i.DefaultView.Url)}) | % { New-Object object | Add-Member -NotePropertyMembers $_ -PassThru }

And replace line 25 with:

write-Output ($web.Title + "," + $list.Title + "," + $list.BaseTemplate + "," + $web.Url + "/" + $i.DefaultView.Url) | Out-File $SaveLocationFinal -Append

Now your report will include all list types, and an added column to specify that list type. Note that this will take a while to run in larger environments.

You could also modify the script to only display in PowerShell (delete lines 23-25 and 31-33) or only export to CSV (delete lines 19-22 and 31-33) but I wanted both outputs for my purposes.

Idea: Take it further with Power BI

Take this to the next level by automating the PowerShell script to run on a schedule exporting results to a folder Power BI reads on an automatic refresh. This is an easy way to get a hands-off dashboard of SharePoint usage by list type.

Set default value of multiple lines of text (enhanced rich text type) field in SharePoint new item forms using jQuery

A recent project tasked me with providing a default value for an enhanced rich text field. Most field types allow you to set a default value out of the box. With enhanced rich text (multi-line text field type) there’s no option to set a default value.

I decided jQuery was the route I wanted to go on this one. After Googling for a fair while I finally found a response that helped.

Modifying the answer provided there, I simplified the code that worked to:

<script src="https://sharepoint.contoso.com/SiteAssets/jquery.min.js" type="text/javascript"></script>
<script>
$(function () {
                $('div[id$="TextField_inplacerte"').text("Test");
});        
</script>

Just save that code as a .js file you upload to your site. Then reference the saved .js filepath via a Content Editor Web Part placed on the new item page (newform.aspx).

Not familiar with jQuery?

The first line of the script needs to reference jQuery. In some environments this needs to be hosted in your site (I typically use Site Assets for this). In other cases, you can just reference it hosted elsewhere (not recommended, but could work for testing/short-term in some environments). I used the code from http://code.jquery.com/jquery-latest.min.js and saved its contents as a .js file of my own that I uploaded to Site Assets. Then just replace the URL in the first line of the script with the URL of the file you uploaded to Site Assets.

More than one enhanced rich text field?

The script looks for any enhanced rich text field, and in my case I only had one in the form so it was fine. If you have more than one, just replace TextField_inplacerte with the full ID of the rich text text field on your form.

Hint: User developer tools with F12 in the browser then select the field. The ID you want will end with TextField_inplacerte and likely start with your field name.

Create a custom permission level in SharePoint

Photo by Markus Spiske from Pexels

I’m often asked for a way to modify permissions beyond what’s available out of the box, but without using workflows.

There are settings that allow item-level permissions in lists (List Settings > Advanced Settings > Item-level Permissions) so that users can only see and/or edit their own items, but this may not solve your need. If so, ta-da! If not, keep reading.

Click to enlarge

In this post, I’ll cover one solution in which we create a custom permission level at the site level that we’ll assign to a group on a specific list’s permissions. This new, custom permission level will be the same as the Contribute level (out of the box) but removes the ability for users to delete items or versions.

Creating a custom permission level involves a couple main steps:

  1. Create the new permission level.
  2. Change permissions on the list so that the group of users who should have the new permission level are assigned the new permission level.

Create the new, custom permission level

1. Go to Site Settings

2. Under Users and Permissions, select Site permissions

3. Click Permission Levels

4. You could click Add a Permission Level but I typically prefer to copy an existing level (like Contribute) and just make a couple small changes. For this tutorial, I’m going to select Contribute.

Click to enlarge

If copying a level, scroll down to the bottom after selecting a level and click Copy Permission Level.

5. Name and describe the new permission level, then check and uncheck as needed to create the permission level desired. In my example, I want to copy Contribute, but remove the delete ability so I’ve unchecked the two options involving deletion of items and versions.

Click to enlarge

6. Scroll down and click Create.

Change permissions on the list

Now we need to assign our new permission level to users on the list for which we’re preventing deletion.

1. Go to List Settings.

2. Under Permissions and Management, select Permissions for this list.

3. Select the box next to the name of the group for which you’re modifying permissions.

4. Click Edit User Permissions from the top ribbon menu.

5. Uncheck the current permission level assigned to the group, and check the new custom permission level.

6. Click OK.

Change the email address used for Access Requests on all SharePoint sites and subsites in a web app using PowerShell

Photo by Karolina Grabowska from Pexels

Perhaps you’ve changed SharePoint administrators or a site owner or two recently. Where are the SharePoint site Access Requests they were receiving now going?

This post covers two PowerShell methods of updating the email address used across all sites in bulk:

  • Replace the email address used on ALL sites, no exceptions (reset all requests throughout the web app to be sent to one address)
  • Change all instances of a specific address to a replacement across ALL sites (i.e. replace the former site owner’s address used in 12 sites’ Access Request Settings with the new site owner’s address)

The second method is particularly nice because it eliminates any guesswork involved in wondering where the former admin/owner may have been listed as the recipient.

Replace the email address used on ALL sites

To modify the email address used for all SharePoint sites and subsites in a web app, run the PowerShell script below from a SharePoint server. You’ll need to replace the $webapp and $requestemail values at the top.

Caution: This action cannot be undone. It replaces the Access Request email on all sites and subsites.

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue 
  
$webapp = Get-SPWebApplication https://sharepoint.contoso.com  
$requestemail = "new-request-recipient@demo.com"   
  
foreach($site in $webapp.Sites)  
{  
   foreach($web in $site.AllWebs)  
   {  
     $url = $web.url  
     Write-host "Checking "$url  
     if (!$web.HasUniquePerm)  
     {  
            Write-Host "Site inherits Access Request settings from parent." -ForegroundColor Yellow  
     }  
     else  
     {  
       if($web.RequestAccessEnabled)  
       {  
            Write-Host "Site utilizes Access Requests."   
            $web.RequestAccessEmail = $requestemail  
            $web.Update()  
            Write-Host "Email changed to " $requestemail -ForegroundColor Green
        }  
            else  
      {  
            Write-Host "Site is not utilizing Access Requests." -ForegroundColor Yellow  
      }  
   }  }
}

Replace all instances of a specific user across all sites in the web app

Perhaps a particular individual left their site owner/admin role and you just want to replace any instance of THAT user in Access Request settings throughout the web app. In that case use the following script instead (updating the three parameters at the top, $webapp, $oldrequestemail, and $newrequestemail):

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue 

$webapp = Get-SPWebApplication https://sharepoint.contoso.com
$oldrequestemail = "old-request-email@contoso.com"  
$newrequestemail = "new-request-email@contoso.com"   

foreach($site in $webapp.Sites)  
{  
   foreach($web in $site.AllWebs)  
   {  
        $url = $web.url  
          Write-host "Checking "$url  
          if (!$web.HasUniquePerm)  
          {  
                 Write-Host "Site inherits Access Request settings from parent." -ForegroundColor Yellow  
     }  
          else  
     {  
            if($web.RequestAccessEnabled)  
                   {  
                   if($web.RequestAccessEmail -eq $oldrequestemail)
            {
            Write-Host "Site utilizes Access Requests sent to old email ("$web.RequestAccessEmail")." -ForegroundColor Red
            $web.RequestAccessEmail = $newrequestemail  
            $web.Update()  
            Write-Host "Email changed to" $newrequestemail -ForegroundColor Green
        }  
                    else 
               {            
                    Write-Host "Email ("$web.RequestAccessEmail") does not match old email address. No change made." -ForegroundColor Yellow
               }}
            else  
      {  
            Write-Host "Site is not utilizing Access Requests." -ForegroundColor Yellow  
      }  

   }  
   }  
   }

Note: If you’ve changed the Access Request recipient and the new person is receiving a “Sorry, this site hasn’t been shared with you” error when attempting to approve requests, check out this post for help.

Credit and gratitude to Ketak Bhalsing and his post on bulk-updating for pointing me in the right direction on this one.

Solution: SharePoint site owner with full control unable to approve access requests; site is missing a default members group

In this post I’ll cover two symptoms commonly seen when subsites evolve from inheriting permissions (using existing groups) to being given unique permissions (having their own groups at the site’s level).

Symptoms

  • A site owner with full control gets “Sorry, this site hasn’t been shared with you” when trying to approve access requests.
  • When reviewing Access Request Settings, a user or owner sees the message “Members cannot share this site because this site is missing a default members group.”

Cause

Chances are the site was never set up with default, unique permissions groups. Perhaps the creator of the site chose to inherit permissions from the parent (using existing groups from a hierarchical level higher than the new site), then later decided to manually build out groups that resemble the traditional visitors, members, and owners groups at the new site’s level. Or perhaps the default groups were deleted. Either way, the following solution should set it straight:

Solution

We need to either officially designate or build new default groups for the site, using the same dialog you see when creating a new site. Since we can’t “reconfigure” the site with a wizard, we need to manipulate the site’s URL a bit to get to the configuration screen we’re looking for.

Add “_layouts/15/permsetup.aspx” to the end of the site URL. For example, it may resemble sharepoint.contoso.com/sites/ABC/EA/_layouts/15/permsetup.aspx. This takes you to the permissions setup page.

IF YOU HAVE GROUPS YOU WANT TO SET AS THE DEFAULTS

Perhaps after site creation, you created groups intended to be used like the visitors, members, and owners groups. Go to your site’s _layouts/15/permsetup.aspx page and simply:

Leave “Use an existing group” selected, and change the dropdown for each to the groups that were created and intended to be the new defaults. Click OK when finished. This will make them “official.”

IF YOU DON’T HAVE GROUPS YOU WANT TO SET AS DEFAULT

Change “Use an existing group” to “Create a new group” for at least the Members and Owners options. Here you can add the appropriate persons to each group, or add them at a later time via Site Settings > Site Permissions. Be sure to add your owners (approvers/permissions managers) to the new owner group.

Disable modern page comments globally for all sites in SharePoint 2019 and SharePoint Online

Photo by Lukas from Pexels

Those of you with Office 365/SharePoint Online have a simple path to disabling modern page commenting via the admin center. But for those of you, like myself, who also work in SharePoint 2019, your method involves PowerShell and is a bit more laborious. In this post, I’ll cover both methods: Disabling comments via PowerShell for SharePoint 2019 (server) and then via the admin center for SharePoint Online (O365).

Firstly, why disallow commenting altogether instead of giving site owners and page editors the choice? Some organizations have compliance regulations that require any sort of conversational transaction to meet certain criteria. Perhaps you only need to disable commenting temporarily while you build a case to prove compliance. Or perhaps you’ve determined it’s not compliant or have some other reason for wishing to globally disable page comments.

No matter your reasoning, here’s the how:

Disable modern page/news comments globally in SharePoint 2019 via PowerShell

The script below will iterate through all site collections in a web application, and all of the subsites within each site collection, and turn off page comments for sites that haven’t already had them disabled. This does not delete page comments. If you re-enable the feature later, the comments that were there previously will be restored.

Log onto a SharePoint server and run the following PowerShell script, replacing the site URL with your own web app’s URL:

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
Get-SPWebApplication "https://sharepoint.contoso.com" | Get-SPSite –Limit All | Get-SPWeb -Limit All | ForEach-Object {
    if($_.CommentsOnSitePagesDisabled = "False")
    {
        $_.CommentsOnSitePagesDisabled = $True
        $_.Update()
        Write-Host "Disabled page comments on " $_.URL -ForegroundColor Green
    }
    else
    {
        Write-Host "Page comments already disabled on " $_.URL -ForegroundColor Yellow
    }
}

If you want a read-out to validate current status (False = not disabled, True = disabled), you can run the following script (again, replacing the URL with the web app of your own):

Get-SPWebApplication "https://sharepoint.contoso.com" | Get-SPSite –Limit All | Get-SPWeb -Limit All | ForEach-Object {
Write-Host $_.CommentsOnSitePagesDisabled $_.URL
}

Disable modern page/news comments globally in SharePoint Online/O365

1. Go to the SharePoint admin center at yourdomain-admin.sharepoint.com and select Settings from the left navigation menu.

2. Select Pages.

3. Uncheck Allow commenting on modern pages and click Save.