April 3, 2020

Remove Wiki tab during provisioning of Teams channels

Task

Remove Wiki tab during provisioning of Teams channels.

Solution

As I’m not cloning my Team, but instead enumerating channels and tabs of the source Team used as a template, it was quite straightforward to include the Wiki tab remove logic to the stage where I’m anyway fetching Tabs for each source Team Channel.

This one includes Polly retry-logic for the DELETE operation (my first time using Polly so there’s probably lot of room for improvement).

It would also be nice to bake the Polly retry-logic into the GraphServiceClient itself instead of surrounding all calls. If you know it has already been done, please comment below. I’m always looking forward to improve my code.

I’m currently using version 3.1.0 of Microsoft.Graph library.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// init polly
var retry = Policy
    .Handle<ServiceException>(ex =>
        (ex.StatusCode == System.Net.HttpStatusCode.NotFound
        || ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests
        || ex.StatusCode == System.Net.HttpStatusCode.BadGateway)
        )
    .WaitAndRetryAsync(60, _ => TimeSpan.FromSeconds(5)); // retry every 5 secs for 5 minutes
    
// create new team

// get new team channels
var newTeamId = newTeam.Id;
var newTeamChannels = await graphClient.Groups[newTeamId].Team.Channels.Request().GetAsync();

foreach (var chan in newTeamChannels)
{
    chan.Tabs = await graphClient.Teams[newTeamId].Channels[chan.Id].Tabs.Request().Expand("TeamsApp").GetAsync();

    // remove wikis right here, right now
    for (int i = chan.Tabs.Count - 1; i >= 0; i--)
    {
        if (chan.Tabs[i].TeamsApp.Id == "com.microsoft.teamspace.tab.wiki")
        {
            try
            {
                await retry.ExecuteAsync(async () =>
                {
                    await graphClient.Teams[newTeamId].Channels[chan.Id].Tabs[chan.Tabs[i].Id].Request().DeleteAsync();
                });

                chan.Tabs.RemoveAt(i);
            }
            catch (Exception ex)
            {
                logger?.LogInformation($"Error removing wiki tab on channel: {chan.DisplayName}. {ex.Message}");
            }
        }                            
    }
}

March 31, 2020

SharePoint: Story of an orphan assemblyBinding and a misleading SPSite error message

Error

I have .NET console application doing all kinds of magic, but as a first step it creates instance of SPSite using the same piece of code I’ve used hundreds, if not thousands, of times over the last two decades:

using(var site = new SPSite(“http://xyz”))

But no, after few seconds it threw exception:

The Web application at http://xyz could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.

Thoughts

Having done all the suggested steps in here (including comments), and in all blog and forum posts related to this issue on the internet, I finally looked at ULS logs. Should’ve done it sooner. It had logged this error message when creating instance of SPSite:

Unexpected error while verifying backwards compatibility for [SPConfigurationDatabase] [e6f4eb31-959b-48da-a3a9-44d23f908f2e]: Microsoft.SharePoint.Upgrade.SPUpgradeException: Failed to call GetTypes on assembly Microsoft.Office.Server.Search, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c. Could not load file or assembly 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified. Could not load file or assembly 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified. Could not load file or assembly 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified.   

What on earth, I don’t use Newtonsoft.Json in my project…?!

But I did! Only for a short while, though.

During some stage of the development I had added Newtonsoft.Json from NuGet, then removed it. It left orphan assemblyBinding reference pointing to Newtonsoft.Json in the app.config of the console application.

So basically my app.config in Visual Studio eventually the consoleapp.exe.config looked like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
     <startup>
         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
     </startup>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
         <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
         <bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0"/>
       </dependentAssembly>
     </assemblyBinding>
   </runtime>
</configuration>

This caused the slightly misleading Exception being thrown when doing the SPSite thing.

Solution

I removed the orphan <assemblyBinding></assemblyBinding> under <runtime> in my app.config, rebuilt the app, and sun started shining, and all was good:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
     <startup>
         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
     </startup>
</configuration>

February 4, 2020

SharePoint: Web part SPUserCodeNoAvailableServersFoundException error

Problem

Web part is not loading, you see exception SPUserCodeNoAvailableServersFoundException.

Solution

Start “Microsoft SharePoint Foundation Sandboxed Code Service”.

  1. Go to Central Admin
  2. Go to System Settings –> Manage services in this farm
  3. At “Microsoft SharePoint Foundation Sandboxed Code Service” click “Enable Auto Provisioning”

January 30, 2020

SharePoint: Setting list view specific persisted spcolumnsize programmatically

Problem

I needed to set default list view column widths programmatically on SharePoint Online modern list. I was using PnPjs to create and configure the list.

Thoughts

In modern SPO list view you can change column width, it is then temporarily stored in browser Local Storage as spcolumnsize-unsaved-VIEWGUID. However, you also have the option of saving it into View somehow, as asterisk (*) appears next to view name on SharePoint and you can "Save View As" to persist the column width somewhere. After saving the view even if you remove the spcolumnsize-unsaved-VIEWGUID Local Storage key, and refresh the page, it comes back as spcolumnsize-VIEWGUID, so clearly it is stored somewhere in SharePoint.

Solution

Column widths are persisted in ListViewXml view property inside ColumnWidth element as FieldRef elements. Do note that the FieldRef Names must be column display names, NOT internal field names. I didn’t implement fancy XML parsing, just injected the ColumnWidth element at the end just before ending View tag.

// set field widths
let lv: any = await list.defaultView.select('ListViewXml').get();

let lvXml: string = lv.ListViewXml.replace(
'</View>', '<ColumnWidth><FieldRef Name="Column title 1" width="370" /><FieldRef Name="Some other column" width="90" /></ColumnWidth></View>');

await list.defaultView.setViewXml(lvXml);

December 12, 2019

SPFx: Changing Folder Content Type using PnPjs

Task

Needed to create Folders with custom Content Type in SharePoint document library from my SPFx web part, as there was a need to add some custom fields, such as Description, to the Folder item.

Solution

This one took a few hours to figure out, you need to do it in few steps as you cannot change the content type of a folder using sp.web.folders…update as REST API doesn’t allow ContentTypeId parameter when updating Folder content types, and you will get error:

“The property 'ContentTypeId' does not exist on type 'SP.Folder’. Make sure to only use property names that are defined by the type”

Also if you would use …folder.getItem(), it will fail if folder has special characters.

  1. Create folder as normal Folder
  2. Get list item ID of the folder
  3. Update folder as a list item
let etfn = await sp.web.getList(listUrl).getListItemEntityTypeFullName();

// first create folder
let far: FolderAddResult = await sp.web.folders.add(listUrl + '/' + targetParentFolderName + targetFolderName);

// then get list item ID of the folder
let fData: any = await sp.web.getFolderById(far.data.UniqueId).select('ID').listItemAllFields.get();            

// then get folder as list item
let item: Item = sp.web.getList(listUrl).items.getById(fData['ID']);

await item.update({
    ContentTypeId: 'CUSTOM_FOLDER_CONTENTTYPE',
    PF_FolderDescription: 'Some description...'
}, '*', etfn);

September 17, 2019

Office Online Server: X-WOPI-ServerError "Verifying signature failed"

Problem

After migrating from old OOS (WAC) server to new one, integration between SharePoint and Office Online Server stopped working, and no documents could be opened in browser.

Looking at the network traffic using WireShark, you see X-WOPI-ServerError "Verifying signature failed".


Solution

  1. Ensure HTTPS (TCP 443) flows from SP servers to OOS
  2. Run Update-SPWOPIProofKey -ServerName YOUR_OOS_URL
This will update the required keys in SharePoint and documents can again be opened in browser.

September 4, 2019

SharePoint Online: Hiding "Shared with Us" link in Modern Menu

Problem

For apparently nearly 3 years, people have wanted to hide the “Shared with Us” menu item that is displayed in the Current navigation on modern SharePoint list views. The menu item is injected as 4th item on the menu on sites that are Group based. Item doesn’t exist on traditional Team sites although they would be modern.



Thoughts

There is no way to hide it by modifying the Current navigation, nor by disabling any feature.

Solution

Best I came up with was to inject CSS on all tenant sites, and hide it with simple CSS style. It’s not perfect solution, as the CSS is injected using JavaScript meaning when page is loaded, it will momentarily display the “Shared with us” link before the JavaScript is loaded and CSS is injected.

Good thing, though, is that it does work across all sites with just one deploy and can be easily modified.

Let’s do this

  1. Clone and build my fork (https://github.com/jpalo/react-application-injectcss) of the handy CSS injection SPFx extension made originally by hugoabernier, thanks Hugo!
    - Hugo’s latest version is for SPFx 1.8.0, mine is 1.9.1, so in case you want to use the latest (as of writing) SPFx version, use my fork.
    - My code also assumes the custom.css is in /sites/cdn/Style%20Library/custom.css, but that is easily modified in case you want to use another location.
  2. Before deploying the .sspkg to the tenant, ensure /sites/cds site collection exists, and upload the custom.css from the of the SPFx project to /sites/cdn/Style%20Library/custom.css
  3. Depending on the user base of your tenant, you will need to assign proper Read permissions to the /sites/cdn site collection for all users (and possibly Guests) to be able to read the custom.css file