How to generate a sample data test file using PowerShell

5gb sharepoint drag and drop

Using PowerShell, you can quickly create a test file on your computer. I use these for testing SharePoint attachment upload sizes.

You can generate files in mb, gb, etc. easily. This example generates a 5gb test file in  C:\Users\MyUserName\AppData\Local\Temp called testfile.txt

$path = "$env:temp\testfile.txt"
$file = [io.file]::Create($path)
$file.SetLength(5gb)
$file.Close()
Get-Item $path

5gb test file

Example of the above file uploaded to SP using drag and drop:

5gb sharepoint drag and drop

SharePoint Online- Bad service principal breaks SharePoint Admin API Access “Global Service Principal ID Error: Error: {}” Resolved

Could not retrieve Global Service Principal ID Error

We recently ran into an issue when creating SPFX Azure Active Directory App registrations using an O-Auth Implicit flow that our SharePoint Admin Center API Access page then gave an error:

Could not retrieve Global Service Principal ID Error: Error: {}
Could not retrieve Global Service Principal ID Error: Error: {}
Could not retrieve Global Service Principal ID Error: Error: {}

And when using O365 CLI, I was getting the same error trying to add a new serviceprincipal or list them:

o365$ spo serviceprincipal grant list
Error: Another object with the same value for property identifierUris already exists.

Error: Another object with the same value for property identifierUris already exists.
Error: Another object with the same value for property identifierUris already exists.

So now I am completely locked out of interacting with SharePoint Online to Azure AD Applications until this is fixed.

I opened a case with SharePoint Online MS Support and they said its on the Azure side but they would look into it for me.

After a few days they got back to me and said it was due to a bad Azure AD App Registration tagged under SharePoint that is causing the error. GREAT! I now have a place to start.

  1. Go into Azure Active Directory and go to App Registrations and search for “SharePoint”. Your “broken app” is somewhere in one of these two registrations.

    App registrations for SharePoint
    App registrations for SharePoint
  2. Big thanks to this article where it says how to find the bad one, https://github.com/SharePoint/sp-dev-docs/issues/3891#issuecomment-494868401 
    In the API Access admin page of SP admin https://yourcompany-admin.sharepoint.com/_layouts/15/online/AdminHome.aspx#/webApiPermissionManagement, go to view source and search for spfx3rdPartyServicePrincipalId. You will find a GUID, THIS IS THE GOOD ONE. The other application is bad.
    Good one (found in view source of Admin page- “spfx3rdPartyServicePrincipalId”:”ebxxxx-xxxxx-xxxxxxx899d1
  3. No go back into app registrations and map out what GUIDs belong where
    Good ID: “SharePoint Online Client Extensibility Web Application Principal
  4. That means the other one is bad! “SharePoint Online Client Extensibility Web Application Principal Helper
    According to the above article, deleting this one fixes it. AND IT DID!
    Object ID 218 is the good one, and the top one 421 is BAD!
  5. Sure enough, after removing the bad application I can now access the API Access page!

So after removing every single bad VM, app registration or enterprise app, it all comes down to removing the bad “SharePoint Online Client Extensibility Web Application Principal Helper” app registration above. Now everything works as expected and I can approve my app requests!

Big lesson learned!

Now I can do a Enable-SPOTenantServicePrincipal without errors:

Hope this helps and leave any comments below on your experience with this error.

SharePoint Online- Create a new Home Site in an existing tenant. Spoiler- its not available yet.

This week at #MSIgnite, there has been a lot of talk about Home Sites. This was released a few months back as a PowerShell command to transform any modern communication site collection into the O365 SharePoint Online landing page for your organization.

More can be read here: https://techcommunity.microsoft.com/t5/Microsoft-SharePoint-Blog/SharePoint-home-sites-a-landing-for-your-organization-on-the/ba-p/621933

SharePoint home sites are the landing sites for your organization that bring together news, events, content, conversations and video to deliver an engaging experience that reflects your voice, your priorities, and your brand.  Home sites are built on top of familiar communication sites.

UPDATE- SPO Home Sites is not available to the public yet, even on first release tenants. I will update this post once its released. See error command at last step in this post.

1- Create the site

Create a Modern Communication Site Collection that will be your hubsite, if you do not have one already.

1- create new communication site

Give the new site a name, I choose topic and Home Site:

2- new communication site title and url

2- Share the site with users

I shared with everyone except external users

3- share new communication site

3- Set the new site collection as the home site

I am hoping this does a bunch of magic to make it look like the Look Book’s rollup.

Enter the URL of your newly created site collection in the URL below:

Set-SPOHomeSite -HomeSiteUrl https://yourtenant.sharepoint.com/sites/HomeSite

andddd error!

Set-SPOHomeSite : The requested operation is part of an experimental feature that is not supported in the current
environment.

4- set-spohomesite error experimental feature not supported

Wow, that was unexpected.

I have not seen a release date yet, but wanted to post this in hopes it will save someone else the time of creating a site.

Update as of 7/3/2020-

Using 16.0.20212.12000 of SPO Management shell- slightly different error:

 

PS C:\WINDOWS\System32> Connect-SPOService

cmdlet Connect-SPOService at command pipeline position 1
Supply values for the following parameters:
Url: https://m365xIDHERE-admin.sharepoint.com/
PS C:\WINDOWS\System32> Set-SPOHubSite

cmdlet Set-SPOHubSite at command pipeline position 1
Supply values for the following parameters:
Identity: https://m365xIDHERE.sharepoint.com/sites/Eric-PublicTeam
Set-SPOHubSite : Cannot invoke method or retrieve property from null object. Object returned by the following call
stack is null. “GetHubSitePropertiesByUrl

At line:1 char:1
+ Set-SPOHubSite
+ ~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Set-SPOHubSite], ServerException
+ FullyQualifiedErrorId : Microsoft.SharePoint.Client.ServerException,Microsoft.Online.SharePoint.PowerShell.SetSP
OHubSite

PS C:\WINDOWS\System32> Get-SPOHubSite
PS C:\WINDOWS\System32>

Cannot invoke method or retrieve property from null object. Object returned by the following call stack is null. "GetHubSitePropertiesByUrl"

SharePoint Online max attachment and upload sizes for lists and libraries

Overview

I tried finding a few good articles about upload sizes for SharePoint. I was a bit confused with old articles, new ones, One Drive for Business sync tools, classic vs modern and was unsure exactly what sizes to recommend to a client. So, I decided to do a POC with various large dummy file sizes around what I was reading the limitations to be. The results were close to Microsoft’s article, but with a very interesting caveat (see article link note below for more details on the findings)

Test setup

There are a few factors when you want to know max file sizes:

  • Classic interface
  • Modern interface
  • List Attachments
  • Document Libraries
  • Drag and Drop
  • Upload Multiple

My Results

The results are SharePoint lists allow much smaller attachments than document libraries allow uploads. Modern in SharePoint lists actually maxes out at a much smaller file size, whereas modern document libraries upload experience seems almost twice as fast to me.

SharePoint Lists:

  • Classic- 200MB max (Microsoft says 250MB is the max, sorry I did not test this)
  • Modern- 100MB max

SharePoint Document Libraries

  • Classic & Modern: 15GB +

Upload multiple is only for SharePoint 2010 I believe, which I was wondering what happened to it!

Microsoft’s documentation

The Microsoft documentation seems a bit outdated on what is currently allowed:

https://docs.microsoft.com/en-us/office365/servicedescriptions/sharepoint-online-service-description/sharepoint-online-limits#service-limits-for-all-plans

  • File size and file path length– 15 GB. The maximum size for files attached to list items is 250 MB. To learn more about restrictions and limits when using the new OneDrive sync client (OneDrive.exe), see Invalid file names and file types.

Note, the above limit is only in Classic. Modern lists max attachment size is MUCH smaller at 100MB.

Developer Note

Also note, if you are a developer uploading files, the browsers do not like uploads over 1GB. Please post your experience, as this was just a quick POC for a client.

SharePoint Online – Change the Look- Classic vs. Modern

I recently created a Microsoft Office 365 development environment via https://aka.ms/offdp

I was creating an environment to do some SharePoint Modern theming (I created a new Communication Site via the modern Admin interface on my new tenant), but noticed the classic (rather the older) Change the Look interface:

However, on existing client’s tenants, I noticed Change the Look was the modern (or updated) version with the Header, Footer, Theme, and Navigation options:

While reading this article https://support.office.com/en-us/article/change-the-look-of-your-sharepoint-site-06bbadc3-6b04-4a60-9d14-894f6a170818 on the Change the Look, I saw the part mentioning Targeted Release:

So, you must use the First Release option. How to set First Release: https://docs.microsoft.com/en-us/office365/admin/manage/release-options-in-office-365?redirectSourcePath=%252fen-us%252farticle%252ftargeted-release-program-3b3adfa4-1777-4ff0-b606-fb8732101f47&view=o365-worldwide

To set First Release, go to the Office 365 Admin portal and Edit your Release preferences:

Set it from “Standard release” to “Targeted release for everyone” (or selected users if you want):

I believe you must wait a while for the changes to take place. Today is Saturday afternoon, and I will check back Monday.

Update- Its Monday morning (2 days later) and when I checked my new DEV tenant, the Change the Look panel updated to the newest options:

 

Hope this helps!

SharePoint Conference 2018 SPC18 highlights

I am a bit bummed I was not able to attend this years SharePoint Conference, but I will attend next years. I was able to follow quite a bit of SharePoint Conference Las Vegas tweets on my twitter account, https://twitter.com/eschraderMB

Here is a summary of what I noted from the #SPC18 hashtags that I thought was interesting. Also, does anyone know if the videos are available online this year?

 

SharePoint Conference 2018 highlights:

  • Guide to setting up Microsoft Teams successfully: https://www.eekels.net/a-unified-approach-to-personal-happiness-in-microsoft-teams/
  • Full SharePoint library experience coming to Microsoft Teams

  • OneDrive file restore released https://techcommunity.microsoft.com/t5/OneDrive-Blog/OneDrive-Files-Restore-and-Windows-Defender-takes-ransomware/ba-p/188001 Other updates https://t.co/ZlmfBb1Rb4
  • Microsoft Flow not available on-premises, but Flow and Power Apps integration with SharePoint Server 2019 will be supported

  • Session by Daniel Glenn on how to make the change from SharePoint Designer to Microsoft Flow

  • Microsoft Flow SharePoint Page Approval workflow demo video- 30 secs
  • SharePoint Server 2019 software pre-reqs

  • SharePoint Admin – Multi Geo Location feature for helping with GDPR compliance

  • SharePoint Content Query Web Part improved experience (finally)

  • Big SharePoint updates
    • https://t.co/uRvW0iowmQ
    • Row Formatting
    • Microsoft Flow cognitive analysis
    • Image analysis
    • New ways to create/manage lists
    • AI for images

  • New SharePoint Online Infographics
  • SharePoint Realtime list updates without refresh
  • SharePoint Online metadata for modern pages

    • Flow
      • I have to start off with a big update with Microsoft Flow.  You will now be able to share your Flows with Office 365 Groups and SharePoint Lists!  This was one of the biggest complaints and requested features on user voice so I’m glad to see it’s coming soon.  Being able to share a Flow with everyone that has access to a particular SharePoint List, in combination with the already announced Send HTTP request to SharePoint connector makes it a true SP Designer replacement.
    • SharePoint Online
      • New Ways to Create Lists
        • It might seem simple to you but this one excited me the most because it will solve so many problems.  You will now be able to create a new list from an existing list and on the fly select what fields you want to re-use.  And most importantly bring over associated Flows and PowerApps!  For anyone like me that has been customizing SharePoint forms with PowerApps, the biggest complaint is there is no way to move over those customizations but now there will be. This will be available in late 2018.
      • List Editing Updates
        • This feature is going to make several of my clients happy.  In the demo they showed copying data from an Excel file and pasting it directly into a SharePoint list.  They even copied over an additional field that wasn’t in the SharePoint list and it automatically created the column for you.  This should be available in late 2018.
      • Image Analysis
        • I’m pretty excited about this one.  You will now be able to upload pictures of various types:  receipts, whiteboards, business cards and SharePoint will use object recognition to extract any text from that image and place it in a metadata column for you automatically!
      • Power BI Insights
        • PowerBI will now be integrated with your SharePoint lists to show real time analysis of the data in your lists!  
      • Planner Integration with Lists
        • I use Planner heavily to manage my daily tasks for work.  I was stoked to see that there will be a shortcut directly in your SharePoint Lists to allow you to create a Task in Planner for an item in your list!
      • New Location Column
        • One of the new column types announced is the Location column which shows a map with pinpoint of the location right in your SharePoint list column
      • SharePoint Spaces – Mixed Reality in SharePoint
        • This was probably the most hyped announcement of the keynote.  SharePoint Spaces allows you create immersive mixed reality experience within SharePoint as easily as you can create a page.  I see so many possibilities for spaces, particularly around the PowerBI and 3D Map spaces.  No public release date was announced but you can sign up for the preview here (I already did!)  Sign Up for SharePoint Spaces Preview
    • SharePoint 2019
      • Modern Sites, Pages Lists & Libraries in SharePoint 2019
        • On-prem users will now be able to take advantage of modern team and communication sites.  You can also use the modern sharing experience, suite/app launcher (waffle menu) and SharePoint home screen.  Unfortunately, Hub sites aren’t included in this release. 
      • InfoPath Lives
        • InfoPath will continue to be supported in SharePoint 2019.  InfoPath is like a cat…it has 9 lives.  While this might be good news for some people with a lot of legacy InfoPath forms out there, I would still highly encourage you to start using PowerApps instead.  
      • Expanded Character Support
        • Although we all know using special characters like # and % in your file names isn’t the best practice, we also all know that people do it anyway.  SharePoint 2019 will now accept # and % in file and folder names.
      • Longer URL Path
        • The Max URL is being increased to 400 so good news for those with ridiculously long file names and folder structures.
      • Client Side Web Parts
        • You can now build client side web parts in SharePoint 2019
  • Link a SharePoint list item to a Microsoft Planner in 1 click

    • Document libraries inside Microsoft Teams
    • Connect team sites to Teams
    • New Planner integrations
    • Organizational news
    • Hub sites

  • Preview of Visio import/export to Microsoft Flow feature later this year

  • New SharePoint Create a list menu

  • New Product- SharePoint Spaces AI and mixed reality (3d)
  • Microsoft Training Services new product for training and change management resources
  • SharePoint Online Audience Targeting feature is back again

  • Zero time SharePoint
    search with organization custom results (e.g. SharePoint list), custom entities, verticals, filters and answers directly from Search rather than results

  • Office UI Fabric icons- allows you to create and manage your own set of icons instead of loading all 1500+

Azure VM Remote Desktop error- CredSSP encryption oracle remediation

I ran into an issue where I could not Remote Desktop to my Azure VM and was getting this error:

Remote Desktop Connection
An authentication error has occurred.
The function requested is not supported

Remote computer: 13.64.xx.xxx
This could be due to CredSSP encryption oracle remediation.
For more information, see https://go.microsoft.com/fwlink/?linkid=866660

The issue was a March 2018 security update patch that was applied to my CLIENT computer, so I cannot connect to the Azure SERVER Windows Server 2012 R2 Datacenter VM that has not had updates applied recently.

https://blogs.technet.microsoft.com/mckittrick/unable-to-rdp-to-virtual-machine-credssp-encryption-oracle-remediation/

Examples:
1.       If the client is updated and you try to RDP to an Azure VM that was not updated, then it will be blocked and see the error message.
2.       If the client is not patched while server is updated, RDP can still work. But the session will be exposed to the attack.
3.       If both client & server are patched with default setting (Mitigated), RDP will work in a secure way.

I really could not find a solution anywhere that worked. Just a bunch of links to archaic Windows Updates websites from the early 2000’s (reminds me how far we came)

Solution

The solution is to Uninstall the KB on your CLIENT computer so you can install the KB on your SERVER VMs, then you can reinstall the KB on your CLIENT if you would like.

 

Detailed Steps

  1. I UNINSTALLED the KB4103721 from my Windows 10 machine, so I could patch my VM’s and rebooted.
  2. I RDP’ed from my CLIENT to each SERVER that was not patched, and installed the patch (note a different OS so a different KB patch)
  3. Applied Installed a KB on each SERVER and rebooted http://www.catalog.update.microsoft.com/Search.aspx?q=KB4103715
  4. On my CLIENT Windows 10 PC, I reinstalled the KB
    1. Go to https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2018-0886 and find your OS, then click Security Update
    2. Continue to download the appropriate patch (https://www.catalog.update.microsoft.com/Search.aspx?q=KB4103721)
    3. Once downloaded, install the KB

That’s it! I could not find an easy explanation of what KB to uninstall and which one to install.

A side note, if I enabled automatic updates, I think this wouldn’t have happened. My SharePoint VMs are not patched automatically since they are Dev machines.

Hope this helps.

Deleting a large list from SharePoint using PowerShell

I recently ran into an issue trying to delete a list over 5,000 items from SharePoint. I tried using Metalogix Content Matrix to delete the list/site, but they all were bound by the threshold. I realized I had to use PowerShell, but research lead to even this having issues deleting the list. The solution was batches of 1,000 items. It takes a few hours to remove 25,000 items, but I was able to delete the list once the items were removed.

I received this error prior to deleting the items in the list using a script via modern UI Site Contents menu:

“My List- 24453 items- We’re sorry, we had some trouble removing this. You can try again from the settings page.”

Similar error on the list settings page:

“The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator.”

This script on MSDN was able to remove the items so I could then delete the list in the browser: https://blogs.msdn.microsoft.com/ahmedamin/2017/08/03/bulk-delete-sharepoint-items-in-a-large-list-using-powershell/

Here is the script as well:

[code language=”powershell”]

Add-PSSnapin Microsoft.SharePoint.Powershell -ea SilentlyContinue
$web = get-spweb “https://intranet/sites/home”
$list = $web.lists[“My List Title Here”]
$query = New-Object Microsoft.SharePoint.SPQuery
$query.ViewAttributes = “Scope=’Recursive'”
$query.RowLimit = 1000
$query.ViewFields = “”
$query.ViewFieldsOnly = $true
do
{
$listItems = $list.GetItems($query)
$query.ListItemCollectionPosition = $listItems.ListItemCollectionPosition
foreach($item in $listItems)
{
Write-Host “Deleting Item – $($item.Id)”
$list.GetItemById($item.Id).delete()
}
}
while ($query.ListItemCollectionPosition -ne $null)

[/code]

This above script was able to remove the items, then I could delete the list.

All done!

GoDaddy’s domain expiration process explained

I had a good call with GoDaddy today to explain the process of what happens to a domain when it is deleted. I was interested in purchasing a domain that I know was deleted today. Of course, the easiest way is to have the account holder un-delete the domain and transfer it, but in this case the user who deleted it no longer wants to deal with the domain, or me asking to have it transferred (it’s a two-step process and takes time).

When a domain is deleted, the account holder has a few weeks to undo that action. After this time, the domain goes to an expired domains auction. If no one buys it, GoDaddy backorder customers ($25-$35 cost for a backorder, good for a 1-year registration) will purchase it. If you do not have a backorder, you cannot purchase the domain during this time since you are just a normal person. The domain then goes to a secondary auction. The secondary auction expires about 84 days from when the domain was first deleted by the user. That’s why the GoDaddy support team says “about 90 days the domain could be released back for normal purchase” if no one buys it at auction. GoDaddy monitoring on a domain just sends you an email of the status. I suppose you could order a backorder during the auction process if you see no one is purchasing it, then when it closes and goes to backorders you might be able to get it that way.

Here is the time-table for the scenario of what happens to a domain when a user deletes it:

Day Status

0

User deletes domain

26

Expired Domain Auction starts

36

If someone bids, done, domain is sold.
If no one bids, it goes to the first person who Backordered the domain. You cannot simply buy the domain for $9.99, you need to purchase a backorder.

41

3-day floating period the domain is in limbo. Nothing happens here.

43

Secondary Domain Auction starts

84

The domain is released for normal purchase if it didn’t sell

 

So, if the domain you are looking for is any good, it might sell at auction. Otherwise, purchase a backorder for $25-$35 and hope you get it! If it sells in the auction, you are out the $ for the backorder, so it’s a gamble. In this case, I don’t want to pay (gamble) that no one bids and I will get the domain, since I am just interested in trying to list it for sale myself, so I am passing on purchasing a backorder and just letting it go. I might check back in 84 days but I have a felling it will be purchased.

A good rule of thumb I have noticed is if the domain is worth anything, some domain selling company will snatch it up and you will never see it again. Still waiting for ericschrader.com, I have been waiting for this to fall out of auctions and auction resellers for about 5 years now.

But hey, you might get lucky. I let SharePointEric.com expire a few years ago and it was bought at auction by a reseller, and now it’s back for normal purchase for $11.99 from GoDaddy:

GoDaddy has a cool apprasal tool to check the monetary value of a domain: https://www.godaddy.com/domain-value-appraisal/appraisal/?checkAvail=1&tmskey=&domainToCheck=sharepointeric.com

If you have any tips, post them below.

There is so much to purchasing domains now days, its not like the old days (back before emoji’s).

Updating Visual Studio 2017 Enterprise

Yesterday I noticed a release (VS 2017 Update 6) was released, but my Visual Studio was still running Update 3:

The newest update is version 15.6.

While reading how to update Visual Studio, the article mentioned Visual Studio Installer.

https://docs.microsoft.com/en-us/visualstudio/install/update-visual-studio

So I launched Visual Studio Installer and clicked “Update“. Note, the installer did not tell me what version will be installed, but I knew the latest GA release update is 15.6 (VS current release version https://docs.microsoft.com/en-us/visualstudio/releasenotes/vs2017-relnotes)

Now my Visual Studio shows version 15.6, which is the latest current release:

Microsoft Visual Studio Enterprise 2017 (3)

Version 15.6.0

VisualStudio.15.Release/15.6.0+27428.1

Microsoft .NET Framework

Version 4.7.02556

Installed Version: Enterprise

 

All done!

Create a SPFx web part to display SharePoint list data based on locale and Graph API data

This is an example of how I used the SharePoint Framework (SPFx) to create a modern page/modern experience web part that shows SharePoint list data based on a user’s Language/Locale/Country and some other user profile information from Azure Active Directory using the Graph API.

We onboard guest users using Azure AD B2B to our SharePoint Online tenant using a PowerShell script I documented before. We add some additional profile properties in the CSV when we onboard them, such as Office Location (Which we put whatever we want in here), etc. The issue is, SharePoint Online SPFx web parts do not have a way to pull this information. So, we use the Graph API.

User Data

SPFx Web Part

SharePoint List data

Comparison

Display comparison results in web part

This code will even create the SharePoint list for you using an elements.xml/schema.xml file.

There are some holes in the data structures and list columns since I removed some that are client specific. I did my best to match them up.

Solution

    1. Setup your SPFx environment https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment
  1. Install dependencies for jQuery and Bootstrap (if needed). You only need to run npm install if you don’t use the Yeoman generator (this creates the node_modules folder in your solution. Since these files are not in TFS, I run npm install on new environments when grabbing from source control):

[code language=”bash”]
npm install

npm install –save jquery@2

npm install –save bootstrap
[/code]

  1. Open SPFXProject1\config\config.json and add the path for jQuery and Bootstrap.

[code language=”text”]

"externals": {
"jquery": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js",
"globalName": "jquery"
},
"bootstrap": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js",
"globalName": "bootstrap"
}
},

[/code]

  1. Open SPFXProject1\config\package-solution.json and add the Elements/schema assets reference so the SharePoint list can be created. This creates a feature that deploys the list, which is good to know for uninstalling/troubleshooting.

[code language=”text”]…
"features": [
{
"title": "My Web Part Create List Feature",
"description": "Deploys a list named My List for the slider web part, a content type and site columns",
"id": "88d9ea22-0988-4c58-b551-b1c5dc92e211",
"version": "1.0.0.0",
"assets": {
"elementManifests": [ "elements.xml" ],
"elementFiles": [ "schema.xml" ]
}
}
]
…[/code]

  1. Open SPFXProject1\src\webparts\mywebpartname\MySliderWebPart.manifest.json file and add the full width web part zone hack from https://blog.velingeorgiev.com/how-add-spfx-webpart-full-width-column

[code language=”text”]…

"manifestVersion": 2,
"supportsFullBleed": true,

[/code]

  1. Create the elements.xml file in SPFXProject1\SharePoint\assets\elements.xml

[code language=”xml”]<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Title="MyWebPart"
Location="ClientSideExtension.ApplicationCustomizer"
ClientSideComponentId="8ec40281-8c15-4a97-ab64-cc1feeb7a50c"
ClientSideComponentProperties="{"testMessage":"Test message"}">
</CustomAction>
<Field ID="{3246929A-5ECA-4E1E-BC8F-4ED878F3A33A}"
Name="SPFxSliderImage"
DisplayName="Slider Image"
Type="Image"
RichText="TRUE"
RichTextMode="FullHtml"
Group="Homepage Slider"/>
<Field ID="{1837EB2A-0A2B-494B-9509-61AEDF7BB2CB}"
Name="SPFxSliderBGImage"
DisplayName="Slider BG Image"
Type="Image"
RichText="TRUE"
RichTextMode="FullHtml"
Group="Homepage Slider"/>
<Field ID="{CEEFD3D4-212F-48D7-AA8F-B9A23A7C0FFC}"
Name="SPFxOrder"
DisplayName="Order"
Type="Number"
Group="Homepage Slider" />
<Field ID="{1F2EEECC-5AB3-4D46-8656-E54FDEA3A08D}"
Name="SPFxDesc"
DisplayName="Description"
Type="Text"
Group="Homepage Slider" />
<Field ID="{683FD699-2404-4E6B-848C-100E8CF8D38E}"
Name="SPFxBtnLeftTxt"
DisplayName="Button Left Text"
Type="Text"
Group="Homepage Slider" />
<Field ID="{E3312F89-8492-4A54-9129-16170D64DD6F}"
Name="SPFxBtnLeftURL"
DisplayName="Button Left URL"
Type="Text"
Group="Homepage Slider" />
<Field ID="{9801966F-7B8F-4214-83CA-35C83D41A04A}"
Name="SPFxBtnRightTxt"
DisplayName="Button Right Text"
Type="Text"
Group="Homepage Slider" />
<Field ID="{1ECB4C39-8E31-491C-8A5C-6701C458518B}"
Name="SPFxBtnRightURL"
DisplayName="Button Right URL"
Type="Text"
Group="Homepage Slider" />
<Field ID="{8A327CB8-578A-433F-9F00-75D92B13825C}"
Name="SPFxSliderExpire"
DisplayName="Expires"
Type="DateTime"
Format="DateOnly"
Group="Homepage Slider" />
<ContentType ID="0x01009FAF6E59182F44F6AABD475FDC8F883A"
Name="Slider"
Group="My Company Content Types"
Description="Sample content type from web part solution">
<FieldRefs>
<FieldRef ID="{3246929A-5ECA-4E1E-BC8F-4ED878F3A33A}" />
<FieldRef ID="{1837EB2A-0A2B-494B-9509-61AEDF7BB2CB}" />
<FieldRef ID="{CEEFD3D4-212F-48D7-AA8F-B9A23A7C0FFC}" />
<FieldRef ID="{1F2EEECC-5AB3-4D46-8656-E54FDEA3A08D}" />
<FieldRef ID="{683FD699-2404-4E6B-848C-100E8CF8D38E}" />
<FieldRef ID="{E3312F89-8492-4A54-9129-16170D64DD6F}" />
<FieldRef ID="{9801966F-7B8F-4214-83CA-35C83D41A04A}" />
<FieldRef ID="{1ECB4C39-8E31-491C-8A5C-6701C458518B}" />
<FieldRef ID="{8A327CB8-578A-433F-9F00-75D92B13825C}" />
</FieldRefs>
</ContentType>
<ListInstance
CustomSchema="schema.xml"
FeatureId="00bfea71-de22-43b2-a848-c05709900100"
Title="My List"
Description="My List for site"
TemplateType="100"
Url="Lists/MyList">
</ListInstance>
</Elements>[/code]

The above template Types are 100 for Custom List.

 

  1. Create schema.xml with your list columns from the above elements.xml SPFXProject1\SharePoint\assets\schema.xml

[code language=”xml”]<List xmlns:ows="Microsoft SharePoint" Title="Basic List" EnableContentTypes="TRUE" FolderCreation="FALSE" Direction="$Resources:Direction;" Url="Lists/Basic List" BaseType="0" xmlns="http://schemas.microsoft.com/sharepoint/">

<MetaData>
<ContentTypes>
<ContentTypeRef ID="0x01009FAF6E59182F44F6AABD475FDC8F883A" />
</ContentTypes>
<Fields></Fields>
<Views>
<View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" SetupPath="pages\viewpage.aspx" ImageUrl="/_layouts/images/generic.png" Url="AllItems.aspx">
<XslLink Default="TRUE">main.xsl</XslLink>
<RowLimit Paged="TRUE">30</RowLimit>
<Toolbar Type="Standard" />
<ViewFields>
<FieldRef Name="LinkTitle"></FieldRef>
<FieldRef Name="SPFxSliderImage"></FieldRef>
<FieldRef Name="SPFxSliderBGImage"></FieldRef>
<FieldRef Name="SPFxOrder"></FieldRef>
<FieldRef Name="SPFxDesc"></FieldRef>
<FieldRef Name="SPFxBtnLeftTxt"></FieldRef>
<FieldRef Name="SPFxBtnLeftURL"></FieldRef>
<FieldRef Name="SPFxBtnRightTxt"></FieldRef>
<FieldRef Name="SPFxBtnRightURL"></FieldRef>
<FieldRef Name="SPFxSliderExpire"></FieldRef>
</ViewFields>
<Query>
<OrderBy>
<FieldRef Name="ID" />
</OrderBy>
</Query>
</View>
</Views>
<Forms>
<Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="EditForm" Url="EditForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
</Forms>
</MetaData></List>

[/code]

  1. Add import statements to your SPFXProject1\src\webparts\mywebpartname\MySliderWebPart.ts code in Visual Studio so we can reference the above jQuery and Bootstrap code:

[code language=”javascript”]import * as jQuery from ‘jquery’;
import * as bootstrap from ‘bootstrap’;[/code]

  1. Import IWebPartContext from the @microsoft/sp-webpart-base so we can get the users locale from their browser session:

[code language=”javascript”]import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
IWebPartContext
} from ‘@microsoft/sp-webpart-base’;
import { SPComponentLoader } from ‘@microsoft/sp-loader’;[/code]

  1. Import intersection from lodash so we can compare where two arrays intersect easily and the Graph API. Note, the day after I wrote this web part, MS released a new Graph API for SPFx. See link in comment to do it the newer way (recommended):

[code language=”javascript”]// GraphHttpClient is in preview and will be depricated soon. Use new method https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-msgraph
import { SPHttpClient, SPHttpClientResponse, GraphHttpClient, GraphHttpClientResponse } from ‘@microsoft/sp-http’;
import { escape, intersection } from ‘@microsoft/sp-lodash-subset’;[/code]

  1. Now we can jump into setting up the data structures:

[code language=”javascript”]

// All of the list data we get back from SharePoint, REST API response format

export interface ISlides {
value: ISlide[];
}

// Each item in the list from REST API response
export interface ISlide {
ID: number;
Title: string;
SPFxDesc: string;
SPFxBtnLeftTxt: string;
SPFxBtnLeftURL: string;
SPFxBtnRightTxt: string;
SPFxBtnRightURL: string;
SPFxSliderExpire: Date;
SPFxOrder: number;
Office: string;
Language: string;
Region: string[];
}

// Each item in the list’s Publishing HTML Image, since its an extended property we need a second rest call
export interface ISlideItem {
SPFxSliderImage: string;
SPFxSliderBGImage: string;
}

export interface IItemGuid {
value: string;
}

// each actual list item data
export interface slides {
ID: number;
Title: string;
UrlSlideImg: string;
UrlSlideBGImg: string;
SPFxDesc: string;
SPFxBtnLeftTxt: string;
SPFxBtnLeftURL: string;
SPFxBtnRightTxt: string;
SPFxBtnRightURL: string;
SPFxSliderExpire: Date;
SPFxOrder: number;
Office: string;
Language: string;
Region: string[];
}

export interface IUserProfile {
Office: Array<string>;
OfficeString: string;
Locale: string;
Language: string;
Region: string;
CountryCode: string;
}

// User profile info from Graph API
export interface IUserProfileResponse {
[property: string]: string;
}[/code]

  1. On the export statement, load your jQuery from CDN. These lines give some warnings during the gulp bundle/package process. Let me know if you know how I can fix them, but this way works.

[code language=”javascript”]

private _slides: slides[] = [];
public constructor(context: IWebPartContext) {
super();
// FIX LATER- redundant to load jquery twice, but this will load bootstrap JS and CSS. Fix this and top jQuery import
SPComponentLoader.loadCss(‘https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css’);
SPComponentLoader.loadCss(‘https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css’);

SPComponentLoader.loadScript(‘https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js’, { globalExportsName: ‘jQuery’ }).then((jQuery: any): void => {
SPComponentLoader.loadScript(‘https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js’, { globalExportsName: ‘jQuery’ }).then((): void => {});
});

}

[/code]

  1. Now create your REST API call methods (Replace Some List with your SharePoint list name)

[code language=”javascript”]
private _getCurrentUserProfile(): Promise<IUserProfileResponse> {
// Query user properties from office graph API
// Tutorial: https://docs.microsoft.com/en-us/sharepoint/dev/spfx/call-microsoft-graph-using-graphhttpclient
// Graph API properties https://github.com/microsoftgraph/microsoft-graph-docs/blob/master/api-reference/v1.0/api/user_list.md#user-content-example-2-users-request-using-select
let promise: Promise<IUserProfileResponse> = new Promise<IUserProfileResponse>((resolve, reject) => {
// Call the Rest API for current user, get AAD properties.
this.context.graphHttpClient.get("v1.0/me?$select=displayName, officeLocation, preferredLanguage, givenName, country", GraphHttpClient.configurations.v1)
.then((response: GraphHttpClientResponse) => {
//console.log("response.json(): " + response.json());
resolve(response.json());
}).catch((error: any) => {
reject(error);
});

return promise;
});
return promise;

}

private _getSlides(): Promise<ISlides> {
return this.context.spHttpClient.get(`${this.context.pageContext.web.absoluteUrl}/_api/web/lists/getByTitle(‘My List’)/items?select=Title,SPFxDesc,SPFxBtnLeftURL,SPFxBtnLeftTxt,SPFxBtnRightURL,SPFxBtnRightTxt,SPFxSliderExpire,SPFxOrder,Office,Language,Region&$orderBy=SPFxOrder asc`,
SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}

private _getImage(id: number): Promise<ISlideItem> {
return this.context.spHttpClient.get(`${this.context.pageContext.web.absoluteUrl}/_api/web/lists/getByTitle(‘My List’)/items(‘${id}’)/FieldValuesAsHtml`,
SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}

//private _getImageUrl(url: string): Promise<string> {
// return this.context.spHttpClient.get(`${this.context.pageContext.web.absoluteUrl}/_api/web/GetFileByServerRelativeUrl(‘${this.context.pageContext.web.serverRelativeUrl}${url}’)/ListItemAllFields/ServerRelatveUrl`,
// SPHttpClient.configurations.v1)
// .then((response: SPHttpClientResponse) => {
// return response.json();
// })
// .then((item: IItemGuid) => {
// return item.value;
// });
//}

private _itemCarouselIndicators(index: number): string {
if (index == 0) {
return `<li data-target="#myCarousel" data-slide-to="${index}" class="active" style="background-color:#d7d7d7"></li>`;
}
else {
return `<li data-target="#myCarousel" data-slide-to="${index}" class=""></li>`;
}
}

private _userProfile(aadquery: IUserProfileResponse) {
let userProfile: IUserProfile = { Office: [""], CountryCode: "", Language: "", Locale: "", OfficeString: "", Region: "" };
//console.log(userProfile);
// Store result for each property
// console.log("Graph API current user: " + result.displayName + result.officeLocation + result.preferredLanguage + result.givenName + result.country);

// 1. Office (Office)
userProfile.OfficeString = aadquery.officeLocation;
if (!userProfile.OfficeString) {
userProfile.OfficeString = "Los Angeles";
}
//console.log("Office: " + me._userprofile.OfficeString);
userProfile.Office = userProfile.OfficeString.split(",");
console.log("Users Office:" + userProfile.Office);

// 2. Locale (preferredLanguage) – OLD WAY from AAD, not good.
//userProfile.Locale = aadquery.preferredLanguage;
//if (!userProfile.Locale) {
// userProfile.Locale = "en-US";
//}
//console.log("Users Locale: " + me._userprofile.Locale);

// 3. Country Code (country)
userProfile.CountryCode = aadquery.country;
if (!userProfile.CountryCode) {
userProfile.CountryCode = "US";
}
//console.log("Users Country: " + me._userprofile.CountryCode);

// Final formatting of data

// Select current users Lanaguage (spelled out)
// from en-US, just keep the language. We use Country from AAD to determine Country/Region, not locale
let userLang: string = this.context.pageContext.cultureInfo.currentUICultureName;

userLang = userLang.substring(0, 2);

switch (userLang) {
case "en":
userProfile.Language = "English";
break;
case "es":
userProfile.Language = "Spanish";
break;
default:
userProfile.Language = "English";
}
console.log("Users Language: " + userProfile.Language);

// Select current users Region
// Define all US and CA regions, default any other country to Latin America
// Phase 2 or so, make this read from a SP lookup list.
switch (userProfile.CountryCode) {
// USA
case "US":
userProfile.Region = "USA";
break;

// United Kingdom
case "GB":
userProfile.Region = "United Kingdom";
break;

// Mexico (not really needed since default is Latin America)
case "MX":
userProfile.Region = "Latin America";
break;

// Latin America – Default anyone not US or GB to Latin America for now, since thats the 3rd region and is currently the only region with multiple countries (cases)
default:
userProfile.Region = "Latin America";
}
console.log("Users Region: " + userProfile.Region);

return userProfile;
}
private _itemSlideWrapper(item: slides, index: number): string {
// if the buttons are empty, dont display them
let leftButtonHTML: string = "";
let rightButtonHTML: string = "";
let SPFxBtnLeftTxt = item.SPFxBtnLeftTxt ? item.SPFxBtnLeftTxt : ”;
let SPFxBtnRightTxt = item.SPFxBtnRightTxt ? item.SPFxBtnRightTxt : ”;
if (SPFxBtnLeftTxt.length > 0) {
leftButtonHTML = `<a href="${item.SPFxBtnLeftURL ? item.SPFxBtnLeftURL : ‘#’}" style= "margin-right: 20px;" target= "_blank" > <div class="slider-button ${styles.sliderButton}" > ${SPFxBtnLeftTxt} </div></a>`;
}
if (SPFxBtnRightTxt.length > 0) {
rightButtonHTML = `<a href="${item.SPFxBtnRightURL ? item.SPFxBtnRightURL : ‘#’}"><div class="slider-button ${styles.sliderButton}" target="_blank"> ${SPFxBtnRightTxt} </div></a>`;
}
//check if slide is the first (active) or not
let activeFlag: string = "";
if (index == 0) {
activeFlag = "active";
}
//return slide HTML
return `
<div class="${styles.item} item ${activeFlag}" style="background-image: url(${item.UrlSlideBGImg ? item.UrlSlideBGImg : ”})">
<div class="row">
<div class="col col-xs-12 col-sm-6 col-md-6 carousel-image ${styles.carouselImage}">
<img src="${item.UrlSlideImg ? item.UrlSlideImg : ”}" alt="${item.Title ? item.Title : ”}">
</div>
<div class="col col-xs-12 col-sm-6 col-md-6 carousel-text ${styles.carouselText}">
<h1>${item.Title ? item.Title : ”}</h1>
<p>${item.SPFxDesc ? item.SPFxDesc : ”}</p>
<div class="slider-buttons ${styles.sliderButtons}">
${leftButtonHTML}
${rightButtonHTML}
</div>
</div>
</div>
</div>`;
}
[/code]

  1. Finally, the big puppy, the render() method

[code language=”javascript”]
public render(): void {

// only render this once
if (!this.renderedOnce) {
var me = this;
// Get users profile object
this._getCurrentUserProfile()
.then((_userprofileresponse: IUserProfileResponse) => {
//console.log("userprofileresponse: ");
//console.log(_userprofileresponse);

// format the users profile to compare to news
let userProfile: IUserProfile = this._userProfile(_userprofileresponse);

//console.log("userProfile: " + userProfile);

//Go get the slider. Compare to users profile object and determine to display it or not

this._getSlides()
.then((response: ISlides): void => {
// get slider images
response.value.forEach((slide: ISlide): void => {
this._getImage(slide.ID)
.then((data: ISlideItem): void => {
//Fix these image queries to be failsafe if nothing comes back for each slide image
//Slider Image
// get the image out of the FieldValuesAsHtml
let divSliderImg = document.createElement(‘div’);
divSliderImg.innerHTML = data.SPFxSliderImage;
let imgSlider: HTMLImageElement = divSliderImg.firstChild as HTMLImageElement;
// need to do string split inorder to make the url relative
const imgSliderUrl: string = imgSlider.src.split(this.context.pageContext.web.serverRelativeUrl)[1];
//Slider BG Image
// get the image out of the FieldValuesAsHtml
let divSliderBGImg = document.createElement(‘div’);
divSliderBGImg.innerHTML = data.SPFxSliderBGImage;
let imgSliderBG: HTMLImageElement = divSliderBGImg.firstChild as HTMLImageElement;
// need to do string split inorder to make the url relative
const imgSliderBGUrl: string = imgSliderBG.src.split(this.context.pageContext.web.serverRelativeUrl)[1];
//Continue to store data from query for each slide
const item: slides = {
ID: slide.ID,
Title: slide.Title,
UrlSlideImg: `${this.context.pageContext.web.absoluteUrl}/_layouts/15/getpreview.ashx?resolution=2&path=${this.context.pageContext.web.serverRelativeUrl}${imgSliderUrl}&clientType=modernWebPart`,
UrlSlideBGImg: `${this.context.pageContext.web.absoluteUrl}/_layouts/15/getpreview.ashx?resolution=2&path=${this.context.pageContext.web.serverRelativeUrl}${imgSliderBGUrl}&clientType=modernWebPart`,
SPFxDesc: slide.SPFxDesc,
SPFxBtnLeftTxt: slide.SPFxBtnLeftTxt,
SPFxBtnLeftURL: slide.SPFxBtnLeftURL,
SPFxBtnRightTxt: slide.SPFxBtnRightTxt,
SPFxBtnRightURL: slide.SPFxBtnRightURL,
SPFxSliderExpire: slide.SPFxSliderExpire,
SPFxOrder: slide.SPFxOrder,
Language: slide.Language,
Region: slide.Region,
Office: slide.Office

};
console.log("Slide ID:" + item.ID);
let slideOfficeString: string = item.Office + ”;

let slideOffice: Array<string> = slideOfficeString.split(","); //["Los Angeles", "Value 1", "Value 2"];
console.log("Slide Office" + slideOffice);
let userOffice: Array<string> = userProfile.Office; //["Los Angeles", "Value 1"];

let slideLanguage: string = item.Language; //"English";
let userLanguage: string = userProfile.Language; //should this support multiple?;
console.log("Slide Language:" + slideLanguage);

let slideRegion: Array<string> = item.Region; //["USA", "United Kingdom", "Latin America"];
let userRegion: string = userProfile.Region;
console.log("Slide Region:" + slideRegion);

// reset each news items flag for users filter

let flag1: boolean = false;
let flag2: boolean = false;
let flag3: boolean = false;

// compare each user variable to slide item column

// see if users Office is in the slider item
// make case upper
for (let i = 0; i < slideOffice.length; i++) {
slideOffice[i] = slideOffice[i].toUpperCase();
}
for (let i = 0; i < userOffice.length; i++) {
userOffice[i] = userOffice[i].toUpperCase();
}
let resultcompare: Array<string> = intersection(userOffice, slideOffice);
if (resultcompare.length > 0) {
flag1 = true;
}

// see if users language is in the slider item
if (slideLanguage.toUpperCase() === userLanguage.toUpperCase()) {
flag2 = true;
}

//See if users region is in the slider item
// FIX- This should be list driven from the region/country list and UPS.
for (let i = 0; i < slideRegion.length; i++) {
slideRegion[i] = slideRegion[i].toUpperCase();
}
if (slideRegion.indexOf(userRegion.toUpperCase()) > -1) {
flag3 = true;
}

console.log("Compare Result- Office: " + flag1);
console.log("Compare Result- Language: " + flag2);
console.log("Compare Result- Region: " + flag3);

// if all 3 filters match, show the news item to the user

if (flag1 && flag2 && flag3) {
// Print IT! Append the item to the News object to be pushed to the DOM!

this._slides.push(item);
//console.log("Print the slider item!");
}
else {
console.log("Skipping Slide " + item.ID + ". There are " + this._slides.length + " slides that have been added.");
}

})
.then((): void => {
this.domElement.innerHTML = `
<div class="carousel-container">
<div style="height: 15px; width: 100%; background: #333333;"></div>

<div id="myCarousel" class="carousel slide ${styles.carousel}" data-ride="carousel">
<!– Indicators –>
<ol class="${styles.carouselIndicators} carousel-indicators"></ol>
<!– Wrapper For Slides –>
<div class="${styles.carouselInner} carousel-inner" role="listbox"></div>
<!– Side Controls –>
<a class="${styles.carouselControl} carousel-control left" href="#myCarousel" data-slide="prev"><span class="glyphicon ${styles.glyphiconChevronLeft} glyphicon glyphicon-chevron-left"></span></a>
<a class="${styles.carouselControl} carousel-control right" href= "#myCarousel" data-slide="next" > <span class="glyphicon ${styles.glyphiconChevronRight} glyphicon glyphicon-chevron-right"></span></a>
</div>
</div>`;

// add news items to domElement
if (this._slides.length > 0) {

//Quick fix for edit mode saving not doubling query to slides
jQuery(‘.carousel-indicators’).empty();
var today = new Date();

// Sort by date again
var sortResults: slides[] = this._slides.sort(function compare(a, b) {
if (a.SPFxOrder > b.SPFxOrder) {
return 1;
}
if (a.SPFxOrder < b.SPFxOrder) {
return -1;
}
return 0;
});
sortResults.forEach((item: slides, index: number): void => {
if (index <= 3 && item.SPFxSliderExpire <= today) {

jQuery(‘.carousel-indicators’, this.domElement).append(this._itemCarouselIndicators(index));
jQuery(‘.carousel-inner’, this.domElement).append(this._itemSlideWrapper(item, index));
console.log("Slide "+index + " ID: " + item.ID + " appended to DOM.");
}
else {
console.log("Skipping Slide " + index + " ID: " + item.ID + " Expires: " + item.SPFxSliderExpire + " " + item.SPFxOrder + " Total Slides in list:" + this._slides.length + " and today’s date is: " + today);
}
});
}
else {
// no slide items case here
}
// initialize the slider
jQuery(‘#myCarousel’).carousel(‘pause’);

});
});
});
});
}
}
[/code]

  1. Add the SASS (I removed some of the colors so it might not be perfect):

[code language=”css”]
$white: #fff;
$black: #000;
$blue: blue;

@import url(‘https://fonts.googleapis.com/css?family=Open+Sans:300,400,600’);

@mixin transition() {
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
-ms-transition: 0.5s;
-o-transition: 0.5s;
transition: 0.5s;
}

.carousel {
background-color: #3279c3;
font-weight: 100;

.item {
min-height: 400px; /* Prevent carousel from being distorted if for some reason image doesn’t load */
background-repeat: no-repeat;
background-size: 45%;

img {
margin: 0 auto; /* Align slide image horizontally center */
}
}

.carouselInner {
.carouselImage {
text-align: center;
}

img {
width: 100%;
max-height: 400px;
max-width: 500px;
padding: 10px;
}

.col {
@media screen and (max-width: 767px) {
text-align: center;
}
}

.carouselText {
max-width: 510px;
color: $white;
font-size: 14px;
font-weight: 300;
line-height: 25px;
letter-spacing: 1px;
padding: 0 30px;

@media screen and (max-width: 767px) {
text-align: center;
max-width: 100%;
}

h1 {
font-size: 28px;
font-weight: 100;

@media screen and (max-width: 767px) {
font-size: 48px;
font-weight: 300;
}
}

p {
margin-bottom: 30px;
color: $white;

@media screen and (max-width: 767px) {
font-size: 12px;
line-height: 18px;
}
}

.sliderButtons {
display: flex;

a {
background: $white;
border: 0;
border-radius: 4px;
font-family: ‘Segoe UI’, Arial, sans-serif;
font-size: 14px;
font-weight: 400;
margin-bottom: 20px;
text-align: center;
text-decoration: none;
text-transform: none;
width: 50%;

@media screen and (max-width: 767px) {
min-width: 100%;
}

&:hover {
background-color: #394a4f;
border: 0;
text-decoration: none;
}
}

.sliderButton {
border: 0;
border-radius: 4px;
color: $blue;
display: block;
font-size: 14px;
font-weight: 400;
letter-spacing: normal;
padding: 6px 10px;
text-align: center;
text-transform: none;

&:hover {
background-color: #394a4f;
border: 0;
color: $white;
}
}
}
}
}

.carouselIndicators {
/*position: relative;
display: inline-block;
width: 100%;
margin-left: 0;
left: 0;
@media screen and (max-width: 767px) {
margin-top: 50px;*/
bottom: 6px;

li {
background: $white;
width: 10px;
height: 10px;
margin: 0 8px;
border: none;
}

li {
.active {
background: #c0c0c0 !important;
border: none;
width: 10px;
height: 10px;
}
}
}

@media screen and (max-width: 767px) {
.carouselIndicators {
display: none;
}
}

.carouselControl .glyphiconChevronLeft, .carouselControl .glyphiconChevronRight, .carouselControl .iconNext, .carouselControl .iconPrev {
width: 40px;
height: 40px;
margin-top: -10px;
font-size: 40px;

@media screen and (max-width: 767px) {
display: none;
}
}
}

[/code]

  1. Now we are ready to build and deploy! Run these node.js commands to package. Then install your app package file and assets. (I deploy mine to CDN, see the SPFX Web Part CDN tutorial on the MS website https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/hosting-webpart-from-office-365-cdn ):

[code language=”powershell”]gulp bundle –ship
gulp package-solution –ship[/code]

  1. Add the app to your site
  2. On the new My List that is created, add the Language site column (OOTB).
  3. Add some list data to your My List and tag data. (below is a similar list I added the site columns to, its not the same for the slider since this list was for news. That’s all I had for sample data sorry)

  1. Add the web part to your modern page:

  1. It will appear here, if not, ctrl+f5 or check for build errors:

  1. View the web part:

  1. Use F12 Chrome to debug web part

Please comment below any enhancements to the code, or big areas I may have messed up on when sanitizing the data.

Thanks!

SharePoint Online Communication site – How to share with external users

I had an issue where when I tried to share my SharePoint Online Communication Site with an external user, I received an error:

Your organization’s policies don’t allow you to share with these users. Go to External Sharing in the Office 365 admin center to enable it.

But when I went to my O365 tenant, sharing was enabled:

Allow users to invite and share with authenticated external users
Allow sharing to authenticated external users and using anonymous access links

The issue is that sharing is disabled on the site, so we need to use PowerShell to fix this.

Solution

As a SPO tenant admin, open PowerShell and do Connect-SPOService, then enter your tenant-admin.sharepoint.com URL. Supply your global admin credentials.

Then run get-sposite with your site.sharepoint.com URL, but pipe (|) the results to format-list (fl) with the property attribute looking for shar (for sharing)

 

Connect-SPOService

Url: https://tenant-admin.sharepoint.com

Get-SPOSite https://tenant.sharepoint.com/sites/Extranet | fl -prop *shar*

DisableSharingForNonOwnersStatus :

SharingCapability : Disabled

SiteDefinedSharingCapability : Disabled

DisableCompanyWideSharingLinks : NotDisabled

SharingDomainRestrictionMode : None

SharingAllowedDomainList :

SharingBlockedDomainList :

 

So set the sharing to 1 (ExtenlaUserSharingOnly)

set-SPOSite https://tenant.sharepoint.com/sites/Extranet –SharingCapability 1

Refresh the page you want to share and now the external user invite is allowed.

Done! Now you are able to share a modern SharePoint Online site with guest users. A better approach I use is to create a group and share with external users, that way security and content are isolated from employee content.

SharePoint Online blog site- How to edit the homepage

Problem

After about a week of troubleshooting the classic SharePoint Online blog subsite template (BLOG#0), I was FINALLY able to figure out why I could not edit the homepage.

Solution:

There is a site feature called “Site Pages” that needs to be activated. Once activated, you can edit the blog homepage (assuming you have permissions).

Figure 1- Activate the Site Pages feature on your Blog Site in SharePoint Online. Now you can edit the blog homepage!

Figure 2- SharePoint Online Blog Site Edit button fixed

BAM!

Figure 3- The SharePoint Online Classic Blog template is now editable for the homepage. The About this blog image can be removed.

I was lucky to run across this when troubleshooting why a modern SPFx extension would not show on a classic homepage.

Hope this helps!

Similar issues:

SharePoint Online- Enable New Modern experience on root site collection

UPDATE 9/27/2019- Microsoft this month is releasing a way to “Swap” sites. So create a new modern site collection, then swap it with the root. Invoke-SPOSiteSwap https://docs.microsoft.com/en-us/powershell/module/sharepoint-online/invoke-spositeswap?view=sharepoint-ps#description

If the target is the root site at https://tenant-name.sharepoint.com, then the following preparation activities should be performed prior to performing the swap:

  1. Any Featured links defined in SharePoint Start Page at https://tenant-name.sharepoint.com/_layouts/15/sharepoint.aspx will not be displayed after performing the swap. If required, the Featured links should be documented so they can be manually recreated after the swap.
  2. Functionality such as external sharing and application interfaces are dependent on the policies and permissions defined at the root site. Review the source site to ensure that it has the required policies and permissions as per the existing root site. This includes external sharing settings as well as site permissions.

UPDATE 1/30/2019- Still waiting for the below MS Ignite command. While we wait we can try Jeff Jone’s approach of forcing creation of a modern communication site using the classic site wizard with a client side developer trick: https://www.spjeff.com/2018/12/31/video-create-modern-communication-for-root-site-in-tenant/

UPDATE 9/27/2018- At #MsIgnite, Microsoft just announced a way to convert the root site into a modern communication site using PowerShell!
https://twitter.com/jeffteper/status/1045159986291200000?s=20

Enable-CommSite -url https://yourtenant.sharepoint.com $username [email protected] $password puppies123

Note: this might be the tenant admin url? https://yourtenant-admin.sharepoint.com

This new PS command is not yet publicly available. We just demo’ed it at this session in Ignite. (link: https://myignite.techcommunity.microsoft.com/sessions/65744) myignite.techcommunity.microsoft.com/sessions/65744. We hope to start rolling this out to customers by the end of 2018. Thank you for the enthusiasm and interest.

My old post-

I really enjoy the new Modern experience of SharePoint Online communication sites; however, this requires creating a new SharePoint Online site collection at /sites/new site for the path. The client requested to have the root site branded with the Modern experience. However, while I was able to get the page to appear as the modern experience, I could not match it to the new modern communication sites template 100%. Please post any comments if you have any suggestions to convert the root site to match the communication site look and feel.

SharePoint Online Admin center settings

In the SharePoint Online Admin Center, make sure these settings are all default:

SharePoint Lists and Libraries experience New experience (auto detect)

SharePoint Online root site settings

Next, navigate to your root site, yourcompany.sharepoint.com.

Note how the Modern Experiences is only enabled on lists and libraries by default now days:

To create your first modern page, go to your Pages library. From here, you can create a new “Site Page” which contains all of the new modern page experiences:

Now you can add modern web parts, edit layouts, etc.:

Once finished, publish the page.

Setting modern page as homepage

Go to the pages library, then set the new modern page you published as the homepage by clicking “Make homepage”:

Removing left Quick Launch navigation, attempting to match a classic site to a modern communication site (fail)

The page is now modern, but the quick launch is showing. Modern communication sites do not have this.

This is not easy, unless you want to cheat with CSS. But my goal is to replicate the OOTB new Modern communication site experience on classic pages.

I compared the Site Features between a Classic site and a New Modern Communication Site:

The classic site has the following Site Features activated:

Classic Feature Status
Getting Started Active
Mobile Browser View Active
SharePoint Server Enterprise Site features Active
SharePoint Server Publishing Active
SharePoint Server Standard Site features Active
Site Feed Active
Site Notebook Active
Team Collaboration Lists Active
Wiki Page Home Page Active

Maybe some of these features are the culprit, but nothing stood out.

When I compared the navigation, I found that the Communication sites use Current navigation across the top.

I thought this can be set by swapping the masterpage from “Seattle” to “Oslo” on the Classic site, but it did not affect the modern page I created on the classic site. Crazy.

I also noticed there is no Full Width web part on the modern experience:

So as close as you can get it OOTB is:

Update- how to add a full width web part

If you want to add full width content, you can insert a full-width layout/section to your modern page:

Once you add the full width section, you can add a Hero or Image web part. I dont like these, so I created a custom SPFx jQuery Bootstrap carousel and found a hack to allow the custom web part to appear in this special full width region:

SPFx web part full width hack:
https://blog.velingeorgiev.com/how-add-spfx-webpart-full-width-column

Then, I can add custom HTML/CSS/jQuery to the full width region.

I sometimes copy the home.aspx over and over for subpages so I don’t get the big ugly image banner like I would get OOTB on subpages.

Here is an example of something our team has worked very hard on. I have 1 SPFx web part for the carousel (full width hack) and a SPFx extension for the footer. I sanitized it a lot and excluded a lot of the other web parts due to the data:

SPFx SharePoint full width web part carousel slider

Closing thoughts

If anyone knows how to move the left Quick Launch to under the Site title to match the Communication site template, let me know. Also post any comments about other differences you find between the Modern page on a classic site vs the new Modern communication sites (aside from the O365 group).

SharePoint Server Trail period reinstall fix

Today, I was hit with a SharePoint error on a developers VM when I tried to create a new Site Collection in Central Admin:

Sorry, something went wrong

The trial period for this product has expired.

When I tried to view a teamsite homepage, I received this error:

Sorry, something went wrong

The “SiteFeedWebPart” Web Part appears to be causing a problem. Object reference not set to an instance of an object.

Web Parts Maintenance Page: If you have permission, you can use this page to temporarily close Web Parts or remove personal settings. For more information, contact your site administrator.

Obviously, things were not looking good for me.

I tried rebooting, IIS resets, PSConfig, dismounting my content DB and creating a new one, all with no luck.

I thought perhaps it was because I reset my service account passwords (back to the same password due to a time crunch recently) and maybe that was causing the service accounts to have issues, but I didn’t think that was the problem.

Credit to other bloggers

Luckily, I found a few blogs that reminded me I used a SharePoint Trial media to install these developer VMs. AAH HA! But, I cringed thinking I would have to dismount my farm db, reinstall my binaries, and face any challenges from that potential nightmare process. I WAS ABLE TO SOLVE IT!

First, this blog post showed me where to upgrade the SharePoint product key. I went on MSDN, got my key, and updated my version. They key I had previously was giving an error, so try a few different versions of your keys. https://blog.devoworx.net/2017/02/25/extend-sharepoint-trial-period/

Second, this blog post had me run PSCONFIG with a better flag to reset security, http://alstechtips.blogspot.com/2014/01/error-trial-period-for-this-product-has.html

How to fix these errors

First, go to Central Admin > Upgrade and Migration > Convert Farm license type.

Enter your SharePoint Server 2013 product key from MSDN. In my case, I used SharePoint Server 2013 with Service Pack 1- Enterprise key from MSDN.

Note: If you try downgrading Enterprise to Standard, you get this error, so use an Enterprise Key:

This product key cannot be used to convert SharePoint Server Trial with Enterprise Client Access License to SharePoint Server with Enterprise Client Access License.

Once it takes, you will get a success message:

Part 2-

Now that the key is set, you need to run PSConfig, but with a parameter.

Launch command prompt (or PowerShell) as administrator, change your directory to where PSCONFIG.exe is located, mine is in “C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\BIN”

Run the following command:

.\psconfig -cmd secureresources

It takes about 10 minutes or so.

Finally, kick off an IISReset:

DONE! Now you can create a site collection in Central Admin:

You SharePoint farm is no longer in trial.

Please post any comments below.