Unable to download the Exchange Online PowerShell Module – “Deployment and application do not have matching security zones”


If you want to use multi-factor authentication (MFA) to connect to Exchange Online PowerShell, Microsoft states you are required to install the Exchange Online Remote PowerShell Module, and use the Connect-EXOPSSession cmdlet to connect.

To download this, you need to go to the Exchange Admin Center (EAC) for your Exchange Online organization.

  • Head to Microsoft 365 Admin centre https://admin.microsoft.com for your tenant and sign in.
  • In the left hand navigation, expand out Admin Centers and select Exchange
  • A new tab opens and loads up the Exchange admin center.
  • In the navigation select Hybrid, and then click the second configuration button.

  • This downloads and runs Microsoft.Online.CSE.PSModule.Client.application.

It was at this point I got an error message stated, “Application cannot be started Contact the application vendor.”

From clicking on the Details under summary email it stated: “Deployment and application do not have matching security zones.”

The problem here, is that, it is trying to run a click-once application. This only works in IE/Edge. I was accessing the EAC through chrome. Therefore, log into your tenant again using Edge or IE.

  • Once loaded, you will find Microsoft Exchange Online PowerShell available in your Windows 10 menu. Also, a PowerShell window will open ready for you to connect to Exchange online using PowerShell.

Connect-EXOPSSession -UserPrincipalName <your UPN>

Summary: When downloading the Microsoft Exchange Online PowerShell from the Microsoft Exchange Admin centre, ensure you have logged in using IE or Edge.

Advertisements

Access denied when attempting to move SharePoint documents


The other day at work, I had to help solve a problem for a client. They were unable to move documents from one folder to another, using the modern move functionality within SharePoint Libraries.

Note: To be able to move a document, you need to have minimum of contribute permission on both the source and destination locations. You need to be able to add a new document to the destination and delete at the source.

The client I was helping was trying to move a document from inside one folder to another folder within the same document library. The client had contribute permission. I had checked their permission at the library, folder and document level, just in case there was broken inheritance. Nothing had broken inheritance. When the client attempted to move the library, they encountered the error message:

Access denied. You do not have permissions to perform this action or access this resource.

This had me stumped for a bit. I was able to move the documents, but I was Site Collection administrator. It was only when I was looking at the views on the list, I noticed a column called Approval Status. This was unusual, as we have a default setup for all the libraries within SharePoint and approval isn’t one of them.

After further investigation, it turned out that Content Approval was turned on within versioning settings of the library.

The client didn’t have the ‘Approve Items – Approve a minor version of a list item or document’ permission in this library. It was agreed that I could turn the Content Approval status off as it shouldn’t been turned on in the first place. Soon as this was turned off, the client was able to move documents from one folder to another successfully. I didn’t test it, but I would assume that if I added the client to a group that had the Approve Items permission, they would have also been able to move the documents with Content Approval status turned on.

Reference: https://mydigitalworkplace.wordpress.com/2018/05/20/10-things-about-copy-and-move-sharepoint-online/

Programmatically change the New Menu in SharePoint Online using PowerShell


Back in October 2018 Microsoft changed the way the New menu on a document library worked. You can now edit what is shown in the New menu directly from the library.

I needed to know how to do this in code, and it looked like PNP automatically does this for you. After doing a get-pnpprovisioningtemplate, when you look at the list you will see a XML element called NewDocumentTemplates, which has a JSON formatted string. The property NewDocumentTemplates exists on the view of the list. This made me think that you could have different menu items for different views, but this doesn’t seem to be the case.

<NewDocumentTemplates>[
{"templateId":"NewFolder","title":"Folder","visible":true},
{"contentTypeId":"0x0101007ADED896313A4943AFE7F07133B1339E","isContentType":true,"templateId":"0x0101007ADED896313A4943AFE7F07133B1339E","title":"Document","visible":false},
{"contentTypeId":"0x0101009348E1CE5767914598D327EAE7EB97D500245281855DF00844BF9BD64ADD983B7D","isContentType":true,"templateId":"0x0101009348E1CE5767914598D327EAE7EB97D500245281855DF00844BF9BD64ADD983B7D","title":"CF New Request","visible":true},
{"templateId":"NewDOC","title":"Word document","visible":true},
{"templateId":"NewXSL","title":"Excel workbook","visible":true},
{"templateId":"NewPPT","title":"PowerPoint presentation","visible":true},
{"templateId":"NewONE","title":"OneNote notebook","visible":true},
{"templateId":"NewXSLForm","title":"Forms for Excel","visible":true},
{"templateId":"Link","title":"Link","visible":true}]
</NewDocumentTemplates>

Now this seems to work, when applying it back to the same list. However, I found that if I set my own content types with visible to false, and then tried on a different list that did have that content type, it didn’t hide it.

So why not?

The reason is because the contentTypeId it is expecting, is the ListContentTypeId, not the actual ContentTypeId. For each list, the ListContentTypeId will be different.

When you add a site content type to a list:

“SharePoint list makes a local copy of the site content type and adds the copy to the content type collection on the list. The new list content type is a child of the site content type. The value of the Id property for the list content type is different from the value of the Id property for its parent site content type, but otherwise the two content types are initially the same.”

https://docs.microsoft.com/en-us/previous-versions/office/developer/sharepoint-2010/ms463016(v%3Doffice.14)#effects-from-adding-a-site-content-type-to-a-list

I’m sure PNP will pick up on this and update the code at some point.

PowerShell script

I decided to write my own PowerShell script to solve this issue. First, I needed to understand the JSON format. For the built in Microsoft menu items the JSON looks as follow:

{
  "title": "Word Document",
  "templateId": "NewDOC",
  "visible": true
}

There are quite a few built in ones, and from using PNP to export out, I was able to find what they were called. (If you find out any more, that I don’t have listed here please let me know.)

  • TemplateId – Name
  • NewFolder – Folder
  • NewDOC – Word Document
  • NewXSL – Excel workbook
  • NewPPT – PowerPoint presentation
  • NewONE – OneNote notebook
  • NewVSDX – Visio drawing
  • NewXSLForm – Forms for Excel
  • Link – Link

Note: If you don’t have a license for Visio, or do not have Forms enabled for your E3 license, then the menu item will not show up anyway.

The JSON format for content types are the following:

{
  "title": "<Content Type Name>",
  "templateId": "<ListContentTypeID>",
  "visible": <true/false>,
  "contentTypeId": "<ListContentTypeID>",
  "isContentType": <true/false>
}

Below is my function script. Set-ListNewMenuItems.ps1 With this script you can hide any content types/default menu items by using the titles.

-Url:’https://tenant.sharepoint.com/sites/demo&#8217; -ListTitle:’Documents’ -ContentTypesToHide:’OneNote notebook’,’PowerPoint’,’Custom CT Name’

You can also hide all default menu items, and let your content types show. It will still show links and folder.

-Url:’https://tenant.sharepoint.com/sites/demo&#8217; -ListTitle:’Documents’ -HideDefault:$true

If you want to hide link and folder too, you would need to include their names in the ContentTypesToHide.

-Url:’https://tenant.sharepoint.com/sites/demo&#8217; -ListTitle:’Documents’ -ContentTypesToHide:’Folder’,’Link’ -HideDefault:$true

After running my script to remove Document, OneNote notebook, Word Document and Forms For Excel

-Url:’https://tenant.sharepoint.com/sites/demo&#8217; -ListTitle:’Documents’ -ContentTypesToHide:’Document’,’OneNote notebook’,’Word Document’,’Forms For Excel’ -HideDefault:$false

As you can see from above, it states that it is Showing Visio drawing. However, on my tenant I do not have a license for it. Below shows you how my New menu has been updated.

The code isn’t perfect, and could do with improvements such as ordering. Please feel free to use it.

SharePoint Online Custom Format View issue with @now and UK Date format


Before you read all of this, I do not have a solution. I currently have an open premier support call with Microsoft regarding this, and when this is fixed, I will update the post.

View formatting is a way to change the look of your Lists/Libraries. Using JSON you apply to the list will change the way it looks. The following link takes you to Microsoft Page about Custom Views. https://docs.microsoft.com/en-us/sharepoint/dev/declarative-customization/view-formatting.

There are two ways to write the JSON, one way is using Abstract Tree Syntax (AST) or you can write in the Excel-style. I will be using the Excel-style.

I have recently started using them for a project I was working on, it was a simple request to change the colour of the row depending on a date column. The rules were:

  • Date equals to today – Leave the row white.
  • If Date is before today – Make the row red.
  • If Date is in the next 7 days – Make the row Orange.
  • If Date is between 7 and 28 days away – Make the row Yellow.
  • More than 28 days – Make the row green.

However, I was noticing that the colours didn’t reflect the correct values. It was only when date changed from the 28th February to the 1st March, the colours changed (still incorrect though) when I realised that the custom formatting didn’t understand UK date formats. Soon as I flipped my Regional Settings for the Locale to be English (United States) the colours displayed correctly.

Example

(Please note I am doing this example on the 8th March 2019)

For this example, I have created a SharePoint List and added an additional Date column. For the purpose of understanding what is going wrong, I have created 2 more text columns called “Date In UK” and “Date in US” where I have put the date values in text for everyone to understand. (e.g A date written like: 03/04/2019, an English person would read this as 3rd April 2019, where an American person would read this as 4th March 2019)

The JSON format I have applied is below, this uses the Excel-Style formatting of using a nested if statement. An excel if statement looks like : =if(logic_test, true, false) When you are doing a nested If statement, the false value, is another if statement. Meaning if 1st logic_test is false, then run the second if statement instead. =if(1st_logic_test, true, if(2nd_logic_test, true, false))

To do the comparison against the current date, you use the @now keyword. If you need to calculate a week date from now you need to add 7 day worth of milliseconds, (7 * 24*60*60*1000 = 604,800,000) @now + 604800000

{
  "schema": "https://developer.microsoft.com/json-schemas/sp/view-formatting.schema.json",
  "additionalRowClass": "=if([$Date] == @now,'',if([$Date] < @now, 'ms-fontColor-neutralSecondary ms-fontColor-redDark ms-fontSize-mPlus ms-fontWeight-bold sp-field-severity--blocked',if([$Date] <= @now + 604800000,'sp-field-severity--severeWarning ms-fontColor-neutralSecondary ms-fontWeight-bold',if([$Date] <= @now + 2419000000,'sp-field-severity--warning','sp-field-severity--good ms-fontColor-neutralSecondary'))))"
}

When this JSON is applied to the list, my list looks like the following:

As you can see from the picture above (if the colours come out right on your screen) the formatting is incorrect.

Item 1 – is Red

Item 2 – is Yellow

Items 3 to 7 are Green.

Remember I’m doing this demo on the 8th March 2019, which should show:

Items 1 to 3 – in Red (Before today)

Items 4 – White (as it’s today’s date)

Item 5 – Orange (In next 7 days)

Item 6 – Yellow (In next 7 to 28 days)

Item 7 – Green (Over 28 days)

So why do I have Item 1 red, and Item 2 yellow, but the rest green. This is because the date is being read in by the formula the wrong way round as an American date. The 4th column “Date in US” is the way the date is being read in.

Item 1 – being read in as the 3rd January, which is before the 8th March – Red

Item 2 – being read in as the 3rd April, which is more than 7 days after the 8th March – Yellow

Items 3 to 5 – being read in as the 3rd July onwards, which is more than 28 days after the 8th March – Green.

Items 6 to 7 – Would have invalid dates in American date format because there is not a 20
or 18 month. It is green because of the last if statement that says If less than 28 days, (true) display yellow, else (false) display green.

Changing the Regional Settings Locale.

I will now go to my Regional Settings and change the Locale. This can be found in site Settings, and under Site Administration select Regional Settings.

Change the Local from English (United Kingdom) to English (United States)

When I go back to my list, the colour formatting looks very different, but correct. (Well almost)

Apart from Item 4 the 8th March 2019 which should be white because it is today’s date, all the colours are now correct according to the JSON formula.

Conclusion

I have tested this same formula on 3 different tenants, and the same happens every time. Therefore, I’m certain it isn’t a problem with my tenant. It doesn’t seem to be just the UK date format either, it affects any date when the format isn’t in the American format of MM/DD/YYYY.

After searching on the Web trying to find a solution, I did come across this github issue https://github.com/SharePoint/sp-dev-list-formatting/issues/92 which has been opened since 5th October 2018. This issue is around calculated columns using dates but reading through the comments it does seem that people are saying they can get it working when using the US date format.

Until Microsoft addresses this bug, using conditional formatting on date columns is pointless, unless you are using the US Date format.

I have yet to try to write the Formula using the Abstract Tree Syntax(AST) way.

PNP Document Library provisioning – “Failed! Specified method is not supported”


Please note: This appears to be fixed in the latest version of PNP 3.6.1902.2.

I’m using PNP templates to create lists on my client site. These templates haven’t changed in months, but suddenly yesterday these templates kept failing with the error message “Specified method is not supported.”

<pnp:ListInstance Title="Templates" Description="" DocumentTemplate="{site}/Templates/Forms/template.dotx" TemplateType="101" Url="Templates" EnableVersioning="true" EnableMinorVersions="true" MinorVersionLimit="0" MaxVersionLimit="0" DraftVersionVisibility="0" TemplateFeatureID="00bfea71-e717-4e80-aa17-d0c71b360101" EnableAttachments="false" ForceCheckout="false">
<pnp:ContentTypeBindings>
<pnp:ContentTypeBindingContentTypeID="0x0101" Default="true"/>
<pnp:ContentTypeBindingContentTypeID="0x0120"/>
</pnp:ContentTypeBindings>
<pnp:Views>
<ViewName="{guid}" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" Type="HTML" DisplayName="All Documents" Url="{site}/Templates/Forms/AllItems.aspx" Level="1" BaseViewID="1" ContentTypeID="0x" ImageUrl="/_layouts/15/images/dlicon.png?rev=44">
<Query>
<OrderBy>
<FieldRefName="FileLeafRef"/>
</OrderBy>
</Query>
<ViewFields>
<FieldRefName="DocIcon"/>
<FieldRefName="LinkFilename"/>
<FieldRefName="Modified"/>
<FieldRefName="Editor"/>
<FieldRefName="_UIVersionString"/>
</ViewFields>
<RowLimitPaged="TRUE">30</RowLimit>
<JSLink>clienttemplates.js</JSLink>
</View>
</pnp:Views>
</pnp:ListInstance>

It turns out the problem was that I was setting the MinorVersionLimit and MaxVersionLimit to 0.

When set to 0, it allowed there to be no limit.

It seems that Microsoft has removed the ability to set this to 0. When trying to set the value through the browser I get a message saying the value needs to be between 1 – 50000.

If I change my template to set MinorVersionLimit and MaxVersionLimit to 50000 it works without any issues.


<pnp:ListInstance Title="Templates" Description="" DocumentTemplate="{site}/Templates/Forms/template.dotx" TemplateType="101" Url="Templates" EnableVersioning="true" EnableMinorVersions="true" MinorVersionLimit="50000" MaxVersionLimit="50000" DraftVersionVisibility="0" TemplateFeatureID="00bfea71-e717-4e80-aa17-d0c71b360101" EnableAttachments="false" ForceCheckout="false">

I’m not sure if this is all related to Microsoft announcement MC138148 that stated that versioning would change, so that it would be on by default for all document libraries and users wouldn’t be able to turn it off. It did state that if you were OK with this change, then there is nothing you need to do, and that the roll out would happen on the 30th September 2018. It’s not quite that date yet, but I’m wondering if that’s the complete date for all tenants that Microsoft puts.

Externally Sharing – SPFX IsExternallyShared Field Customizer


This is my last post of the Externally Sharing series.

This post will show you how I have created a SPFX field customizer, where if it is displaying in the view, it will perform a call to the GetSharingInformation, then iterate through the links -> Invitations and principals results looking for external users. If it finds an external user, it displays a sharing link for the item, or it doesn’t.

 

ExternalShare2

You can find my Git Hub project at the following URL: https://github.com/pmatthews05/spfx-IsExternallyShared

The field customizer is a REACT SPFX project. As the control onInit() it grabs the list id from the pageContext.


@override
  public onInit(): Promise {
    // Add your custom initialization to this method.  The framework will wait
    // for the returned promise to resolve before firing any BaseFieldCustomizer events.
    Log.info(LOG_SOURCE, 'Activated IsExternallySharedFieldCustomizer with properties:');
    Log.info(LOG_SOURCE, `The following string should be equal: "IsExternallySharedFieldCustomizer" and "${strings.Title}"`);
    this.listId = this.context.pageContext.list.id;
    return Promise.resolve();
  }

As the control onRenderCell event fires, I grab the listitem’s ID. Then pass the ListID and the ID of the item to my .tsx control.

@override
  public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
    const id = event.listItem.getValueByName('ID');
    Log.info(LOG_SOURCE, `Loaded onRenderCell: ID: ${id}`);
    const isExternallyShared: React.ReactElement =
      React.createElement(IsExternallyShared,
        {
          id: id,
          listId: this.listId,
          context: this.context
        } as IIsExternallySharedProps);
    ReactDOM.render(isExternallyShared, event.domElement);
  }

The ExternallyShared.tsx file sets its externalShared state to 0. There are 3 values for externalShared.

0 – Loading

1 – Shared

2- Not Shared

The ExternallyShared.tsx file calls the GetSharingInformation/permissionsInformation in a POST REST API call. From the results I get back (assuming successful), I loop through the links, and principals looking for external users. As soon as I find 1 external user I return my results back. The code here isn’t perfect. I should really create an Interface for the returning json results.

export default class IsExternallyShared extends React.Component {
  constructor(props: IIsExternallySharedProps, state: IIsExternallySharedState) {
    super(props, state);
    this.state = {
      externallyShared: 0
    };
  }
  @override
  public componentDidMount(): void {
    Log.info(LOG_SOURCE, 'React Element: IsExternallyShared mounted');
    this._getIfExternallyShared()
      .then(isExternallyShared => {
        this.setState({
          externallyShared: isExternallyShared
        });
      })
      .catch(error => {
        Log.error(LOG_SOURCE, error);
      });
  }
  @override
  public componentWillUnmount(): void {
    Log.info(LOG_SOURCE, 'React Element: IsExternallyShared unmounted');
  }

 @override
 public render():React.ReactElement {
 return (

 

);
}
privateasync_getIfExternallyShared():Promise {
 var response = await this.props.context.spHttpClient.post(`${this.props.context.pageContext.web.absoluteUrl}/_api/web/Lists('${this.props.listId}')/GetItemById('${this.props.id}')/GetSharingInformation/permissionsInformation`, SPHttpClient.configurations.v1, null);
if (response.status!=200) {
 return 0;
}
  var jsonReponse = await response.json();
    let returnValue = 2;
    for (let link of jsonReponse.links) {
      if (link.linkDetails.HasExternalGuestInvitees) {
        returnValue = 1;
        break;
      }
    }

    if(returnValue == 2)
    {
      for(let principal of jsonReponse.principals)
      {
        if(principal.principal.isExternal){
          returnValue =1;
          break;
        }
      }
    }

    return returnValue;
  }

As you can see in the render() method, I have created my own ExternallySharedIcon control. Passing in the externallyShared value.

  

The ExternallySharedIcon.tsx control just displays an 64based image depending on the value.

  1. – Shows the spinning loading icon
  2. – Shows the sharing icon
export default class ExternallySharedIcon extends React.Component{
    private SHARED_ICON: string = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAG00AABtNAWneuvsAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAACkUlEQVRoQ9XaO2gVQRTG8ev7VSiKASGi2PgANYJaxE5IYqdiZavYWAk2IgoWgp1YaaxEG0HUSiy1sooGQnyAoDYqgiGg+ADf/08YGMLJvTP7mNn94FeES3ZPcmdnz8xupyEZwCVM4At+4j3u4QiWoJFZhZv428M7HECjsgGvYRVs+YMzaESW4yWsQns5huy5Cqu4EF+xDtmik/+CVVyoy8iWU7CKijGNecgSTY1WUbF2IEuewiooVrZp9RmsgmIdQtLMxWFo/FoFxRpEsuzFY1iFFPEdSdqLbbgPq4gybqPWrMU1/IZVQBk65k5EZQH6sOL/T7NHn1/AN1gnr8JFBEUFn8U41N66A3zELezDHCiLcAJT8E9WtTuYj65RUcfxGdZBfA+h5iqmq3T0OzpPyLT6A+fR8+6r4tVnWAepiobXObhZRMPzKB5hZo/0AVewEUE5Df8AVbuL9Zgty7AFahP64YZoUDZDX5V14rKeYwi15gask5fxCSehYVJrFkMLBauIIjRXX8caJMluWIUU8QRJ+xTlIKxiYr2CmrbkqfIPyLJS2gWroCJ0596DpKn6ItYejma1ZBexolnDKqYMN40uRO2p80b2AsOoPa1uJZSUzdxSKBpevZq5TYiK2lyNX/9glgdQO63p0/q8mzfQedQnWZ/7tB4Jaqf9rIa1oNGiRQuaEbivWP/FxixorMQuKauckmcKXlKWiS5ALerLbuJa1CjqxpskW1HHtoqGUtJoY2sMVjFFJNvY8qMLvrVbi36q2p1OvrnrMgmroFjZttdb/4Cj9Y+Y9JDPv7MXkfUhnzIKq7AQ2R+zKq1/0K3oVYOYLrZRrxq4rIRe9lBxVtHOW+xHY7Md3V630aaDkU7nH6/PvLG2FyRSAAAAAElFTkSuQmCC";
    private LOADING_ICON: string = "data:image/gif;base64,R0lGODlhIAAgAJEDAMbvwFTOQ37acf///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCgADACwAAAAAIAAgAAACUpyPqcvtD6Oc1AAhALq5HhEEAgKKnlGOX6h6nHa850y7GCzJS0rxO9sDLnS5W+2ITCopxEiTJJT4FNNIdWOcPJdcWyeW9VSvk3G00tx21+y2owAAIfkECQoAAwAsAAAAACAAIAAAAk6cj6nL7Q+jnNQAIUB9IgSxOd0XNldWpup6alPLjOAkx94s1Qv8Yu4KDAqHJh+Fp9DlbjYSjbkz9lDEagk5wFKUA+4WavBOf1mp9YxOOwoAIfkECQoAAwAsAAAAACAAIAAAAkecj6nL7Q+jnNQAIUB9IgSxOd0XNldWpup6aisygu8Rz0dr5/rOU/j0U9QmQ6FHJikmghFm7/lgOolHWDWkVPowLgsXCg6LCwAh+QQJCgADACwAAAAAIAAgAAACQZyPqcvtD6Oc1AAhQH0iBLE53Rc2V1am6npqKzKC7xHPR2vn+s5TeF4DemS2X++INA6UlKDBWYFCfRiXpYrMarcFACH5BAkKAAMALAAAAAAgACAAAAI7nI+py+0Po5zUACFAfSIEsTndFzZXVqbqemorMoLvEc9Ha+f6zlN4XgN6ZLZf74g0zoI25kyJjEqniAIAIfkECQoAAwAsAAAAACAAIAAAAjecj6nL7Q+jnNQAIUB9IgSxOd0XNldWpup6aisygu8Rz0dr5/rOU3heA3pktl/viEwql8ymM1cAACH5BAkKAAMALAAAAAAgACAAAAIynI+py+0Po5zUACFAfSIEsTndFzZXVqbqemorMoLvEc9Ha+f6zvf+DwwKh8Si8YhUFQAAIfkECQoAAwAsAAAAACAAIAAAAiqcj6nL7Q+jnNQAIUB9IgSxOd0XNldWpurKtu4Lx/JM1/aN5/rO9/4vKwAAIfkEBQoAAwAsAAAAACAAIAAAAiqcj6nL7Q+jnNQBIUB9IgSxOd0XNldWpurKtu4Lx/JM1/aN5/rO9/6vKgAAIfkEBQoAAwAsGAAKAAQABAAAAgeEZBGneFAAACH5BAUKAAMALBgAEgAEAAQAAAIHhGQRp3hQAAAh+QQFCgADACwSABgABAAEAAACB4RkEad4UAAAIfkEBQoAAwAsCgAYAAQABAAAAgeEZBGneFAAACH5BAUKAAMALAQAEgAEAAQAAAIHhGQRp3hQAAAh+QQFCgADACwEAAoABAAEAAACB4RkEad4UAAAOw==";
    @override
    public render(): React.ReactElement {
        const image = this._getSharedStatus(this.props.isExternallyShared);
        if (image != "") {
            return (
                <img src="{image}" width="17px" />
            );
        }
        else {
            return ();
        }
    }
    private _getSharedStatus(value: number): string {
        if (value == 0)
            return this.LOADING_ICON;
        if (value == 1)
            return this.SHARED_ICON;
        if (value == 2)
            return "";
    }
}

The github project shows you how to create the column for the list. It is a standard text column, that doesn’t show up in any New/Edit forms. It only shows up in the view field. I’m using a pnp template to add it to the site collection.

Taking it further

The spfx-isExternallayShared field customizer is a very simple version of the one I created for my client. I was concerned with the number of times I was calling the GetSharingInformation, especially on very large libraries with many users. The call is only made for the number of items that are currently displayed on the screen to the user, however, when you start multiplying that by a possible 2000+ users that is a much larger load on SharePoint. Therefore, I moved the calling of the REST API to an Azure Function. Using the uniqueID of the item I was able to also use Redis Cache.

The first person who visited the library for each row would perform the actual GetSharingInformation call within the Azure Function, as there is nothing in the Redis Cache, then store either a 0 (not externally shared) or 1 (externally shared) with the UniqueID of the item as the key in Redis Cache. The second call to the library, the Azure Function would first check the cache, and just return that value.

The downside was the additional cost of running Azure Functions and Redis Cache, and that the externally shared value was no longer up to date and could be an hour out of date.

UPDATE: It turns out moving it to an Azure Function, where the calls are being made by one App account causes throttling issues. The client had over 79k calls made in an hour (many document libraries and users), and the App token that I was using got blocked by Microsoft. After further conversations with technical people at Microsoft, the calls need to be made in the context of the user. Since I’ve implemented the simple solution above, we haven’t noticed any throttling issues. Therefore if you want to move to an Azure Function, any calls back into SharePoint should really be done in the context of the user.

Externally Sharing – GetSharingInformation REST API


Externally Sharing Series:

My last two blogs I have been explaining how to set up your tenant for External Sharing, and as a user how to share a document to an external user. At the end of my last blog post I explained how you can view which documents have been externally shared, by navigating to Site Usage and viewing the Externally Shared tab.

However, as stated previously, this section is flawed in a couple of ways.

  1. It relies on Search. Meaning that documents may not show up for a while.
  2. It only shows documents that have been externally shared, and the external user has viewed the file. This is because there is a flag (ViewableByExternalUser) on the file that gets updated, only after an external user has viewed the file, not when you have shared it.
  3. Following on from the last point, your site could have lots of documents externally shared, but you would never know if no one externally has viewed the file.
  4. Sharing of a folder will show up the folder in the list, (if external user goes directly to the folder). However, any items that are within the folder, which would also be externally shared, do not show up in this list. (Even if the external user has viewed the files)

If you had a content query webpart and want the same results to be brought back as it shows you in the ‘Shared externally’ tab you need to set your Search Query as follows:

Path:"<a href="https://examplesite.sharepoint.com/">https://examplesite.sharepoint.com/</a>" (IsDocument:"True" OR contentclass:"STS_ListItem") ViewableByExternalUsers=TRUE

GetSharingInformation REST API

The above ‘Externally Shared’ tab and search call, although it can bring back documents, it wasn’t what my client really wanted. They wanted to know straight away if the document was externally shared or not.

I emailed Stephen Rice (Product Manager for SharePoint and OneDrive for business) at Microsoft, and he said there wasn’t a current way of tagging the documents if they were externally shared at the time of sharing the document. The ViewableByExternalUsers is only updated when the user enters the document.

What I couldn’t understand was when I click the Info Panel for a document, if it was externally shared, it would state that the item was shared with guests. This was before an external user access the document.

Using Chrome network capture, I investigated what happens when the Info Panel loads up, and one call caught my attention.

https://tenant.sharepoint.com/_api/web/Lists(@a1)/GetItemById(@2)/GetSharingInformation?@a1='listguid'&@a2='itemID'&$Expand=permissionsInformation,pickerSettings

This call is made via a POST call.

Looking at the Preview response and digging further down into the results that returned, the permissionsInformation section contains all the Sharing links, and groups and users that the item is shared with.

permissionsInformation > links > results

Contains an array of link results. This includes internal sharing links, anonymous links and external links.

linkDetails

Important part of the linkDetails:

  • Created & CreatedBy – When the link was created and by who.
  • Expiration – Only used for anonymous links
  • HasExternalGuestInvitees
    Indicates if any users that have been invited are external users
  • IsEditLink – Set to true for contribute, false for read
  • Invitations See below
  • LinkKind – Enumeration (Microsoft.SharePoint.Client.SharingLinkKind)
    • 0 – Uninitialized
    • 1 – Direct
    • 2 – OrganizationView – People within ‘tenant’ Read
    • 3 – OrganizationEdit – People within ‘tenant’ Contribute
    • 4 – AnonymousView
    • 5 – AnonymousEdit
    • 6 – Flexible – Used in External Sharing with email address.
Invitations > results

The invitation sections gathers the information about the person who doing the inviting ‘InvitedBy‘ and who has been invited “invitee”. You also have the exact time and date when the invite was sent out.

linkMembers

I have discovered that the linkMembers only has values after the external user has visited the link.

permissionsInformation > links > results

The principals results are the groups and members that have access to the documents. If the external user has been previous added to your tenant via a site share, or document share before December 2017, then they will have an actual account in your tenant. Anything shared with them will show up here, instead of through the Shared Links. The Paul Matthews shown in the screenshot below is actually my personal email address and therefore an external user.


The role are:

  • 0 – None
  • 1 – Can View
  • 2 – Can Edit
  • 3 – Owner

principal

The principal contains the user or SP Group information. For users (principalType 1) this includes if they are external or not.

How to use this information?

The last section showed you all about the GetSharingInformation REST API call. It has to be done with a POST call, but apart from that, it does obtain all the Sharing Information in regard to that item. The only one thing I would say it is lacking is that if a SP Group that is assigned to that document had an external user in it. (For example, the members group, because you have externally shared the site) the isExternal flag on the principal of that group isn’t set to True.

Now knowing about GetSharingInformation REST API call I was able to create a SPFX field customizer. My next blog post will show you how I built the SPFX field customizer.