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!

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.

SharePoint Server- Renewing SSL certificates quickly

A week ago, my wildcard SSL certificate expired on GoDaddy. It was automatically purchased, but I still had to validate my domain and download a new IIS CER certificate request file.

My old post from a few years ago has some good info on certs, the file types, etc. https://eschrader.com/2014/09/23/sharepoint-2013-iis7-nlb-ssl-certificates-and-godaddy/

This is a quick guide.

The only issue I have with this quick renewal is that I could not export the certs as a PFX, but I was able to get them installed on the server in IIS by completing a CSR

Here are the steps:

GoDaddy automatically renews SSL certificate

GoDaddy has renewed your SSL certificate, but you have to verify your domain using a TXT record they give you (@ is the host field).

Once you verify, you can download the certificate. Note, this is a CER which is a certificate request that has to be completed in IIS.

Download the certificate for IIS

Copy and extract the zip to the server

I chose to delete my old certificates from my computers Personal certificate store.

Once removed, I go into IIS and go to Server Certificated under the machine:

Once in Server Certificates in IIS, click on Complete Certificate Request:

Change the file type to *.* (All files) and find your CER file you copied over:

Enter your certificates friendly name (mine is a wildcard, so I use *.mydomain.com):

Next, go to your SharePoint IIS web apps that use this host header (could be more than one) and edit the bindings and select the new certificate. If you see multiple, this is why I deleted them in my step above. If you get an error saying change this will leave behind an old certificate of the same name, just double check the other web applications in IIS to make sure they are set correctly. Updating one should update them all, but I always check each site in IIS.

That’s it! The certificate is update.

The bad thing is I have to repeat the IIS complete CSR steps on each machine. I would rather export the first one and import PFX certificate files to my other machines, but hey, this is how I got it to work.

Leave any comments below, thanks!

SharePoint 2013 Newsfeed – We’re still collecting the latest news error

If you are a SharePoint admin or use, you have probably seen this error message on your SharePoint 2013 MySite Newsfeed:

“We’re still collecting the latest news. You may see more if you try again a little later.”

sharepoint 2013 mysite company newsfeed error -Were still collecting the latest news

Hopefully this article will help explain a few common scenarios I have ran into, and how to resolve any errors. Please post if you have any tips or suggestions, as I am always looking for thoughts from the community on this.

This article will address:

  • Common reasons for newsfeed data not displaying
  • What is distributed cache?
  • Distributed cache configuration
  • Shutdown/Reboot WFE procedure for maintenance so you don’t lose your cache
  • Repopulating cache (if server stopped unexpectedly)
  • References

Common reasons for newsfeed data not displaying

  1. Someone rebooted all your Distributed Cache servers at the same time
    1. Check the task manager uptime to see how long the servers with distributed cache have been running, or if they were rebooted unexpectedly
    2. Fix: Repopulate cache using PowerShell, or maybe wait a long time for new news
    3. Prevent it from happening again: Shut down one server at a time, stopping the cache first, rebooting, and then starting the cache again.
  2. “Everyone” is empty because it only keeps company conversations for 14 days by default.
    1. Fix: Increase the retention time, or encourage people to post to the company newsfeed (not site newsfeeds). See this article for more information on what appears in site newsfeeds vs company newsfeeds. https://support.office.com/en-za/article/What-items-appear-in-your-newsfeed-bd3d9268-0408-4ad4-bc51-2e4ec5406e16#__toc327280723
  3. Distributed cache is not configured right
    1. Fix: Configure it right J this one is so simple, yet so difficult I find. See configuration below.

What is distributed cache?

Distributed cache is a framework Microsoft uses to quickly host social information in SharePoint within the SP servers ram. This can be enabled on one or many SP servers in your farm.

Official definition can be found on this poster, https://www.microsoft.com/en-us/download/details.aspx?id=35557

What uses distributed cache?

Pretty much anything social, but some of the social data comes from content databases and user profile databases. Company newsfeed posts are stored in distributed cache.

  • Newsfeeds
  • Authentication
  • OneNote client access
  • Security Trimming
  • Page load performance

Distributed cache configuration

Note: Run any scripts/commands logged in as the SPFarm account, and be sure to run SharePoint Management Shell as administrator if you have UAC enabled (like a good administrator)

Caution: configuration deletes the cache, so you will need to repopulate the cache after configuring it.

Determine servers to host the distributed cache service

Usually the WFE servers, not servers running search or excel. AutoSPInstaller has a limit of 4 servers, but typically it does not configure distributed cache correctly.

Configuring Distributed Cache

There is a good article here on these commands, https://technet.microsoft.com/en-us/library/JJ219613.aspx and probably better than this article I am writing. But it’s very long so I wrote this article to get Admins 90%-100% of the way there.

Here is how I have been configuring distributed cache. Thanks Jon for the help!

  1. Use Central Admin or PowerShell to start/stop the SharePoint Distributed Cache service on the desired servers in your farm (usually WFE’s).
    1. Or you can use PowerShell to get/start-spserviceinstance of Distributed Cache on the desired servers. I like to use PowerShell to see what servers are running Distributed Cache within my farm:
      1. Get-SPServiceInstance | where-object {$_.typename -ilike “*distributed*”}
  2. Verify Cache service is running on desired servers:
    1. Use-CacheCluster
      1. Note, this command doesn’t configure the server, but just connects the current PowerShell session to manage the cache cluster it’s joined for the PowerShell management session. It’s actually an alias command for Connect-AFCacheClusterConfiguration.
    2. Get-CacheHost
    3. You should see each server running distributed cache listed above. If not, there might be more work to configure the cache cluster I may have missed in this post. Let me know!
  3. Get current configuration
    1. Get-AFCacheHostConfiguration -ComputerName wfe01 -CachePort “22233”
    2. The Cache Size can be updated, see guide here https://technet.microsoft.com/en-us/library/JJ219613.aspx#memory . For 16GB of ram on our WFE servers, we go with 819MB (~5% of 16GB). Note, changing this requires the distributed cache service to be stopped on the computer you are changing it on. Update-SPDistributedCacheSize -CacheSizeInMB 819
  4. Export config, verify service account for distributed cache, as well as servers.
    1. Export-CacheClusterConfig -Path C:\test.xml
      1. Check max cache size (default is 5% -, no more than 4GB – size depends on services on the server)
      2. Check servers – ensure only WFE (or desired servers are in the cluster)
      3. Check service account – ensure all servers use the same service account (spservice)
      4. Check ports
  5. Warning: After configuration is complete do not ever run Add-SPDistributedCacheServiceInstance or Remove-SPDistributedCacheServiceInstance. It reconfigures the cluster (and usually incorrectly)

Shutdown/Reboot WFE procedure

If you have to do reboots on the WFE for Windows Updates, etc., you might be expecting to lose your Newsfeed cache. Here is the proper procedure to retain the cache.

Summary: shutdown the cache one server at a time, reboot that server, add the server back into the cache cluster. Repeat on next server.

  1. Verify Cache service is running on desired servers (more than one server too is key):
    1. Use-CacheCluster
    2. Get-CacheHost
  2. Reboot SQL Server first if needed. Get this out of the way.
    1. Wait for SQL Server to come back online
  3. Reboot WFE1
    1. Perform these commands on WFE1
    2. Verify Cache service is running on desired servers (more than one server too is key):
      1. Use-CacheCluster
      2. Get-CacheHost
    3. Run Stop-spdistributedcacheserviceinstance -graceful
    4. Verify Cache service is stopped on WFE1. Ensure it is stopped before proceeding:
      1. Get-CacheHost
    5. Reboot WFE1
    6. Verify Cache service is running on WFE1:
      1. Use-CacheCluster
      2. Get-CacheHost
      3. If not, go to Central Admin Services on Server and start Distributed Cache service on WFE1, or use PowerShell.
  4. Reboot WFE2
    1. (Repeat above Step #3, but replace WFE1 with WFE2)
  5. Verify it is running
    1. Verify Cache service is running on desired servers (more than one server too is key):
      1. Use-CacheCluster
      2. Get-CacheHost

Repopulating cache (if server stopped unexpectedly)

Replace URL with your mysite URL. This script will populate each user’s cache using Update-SPRepopulateMicroblogFeedCache and the entire user profile newsfeed cache using Update-SPRepopulateMicroblogLMTCache. I am not sure if I stole this script from anywhere, but part of it is from various user profile scripts adapted to fix the users feed cache.

Note: Run any scripts/commands logged in as the SPFarm account, and be sure to run SharePoint Management Shell as administrator if you have UAC enabled (like a good administrator). Otherwise you will get a .ctor error that will drive you crazy.

Download the script from here: http://pastebin.com/K9yR2pEk

$proxy
= Get-SPServiceApplicationProxy | ? {$_.Name -ilike
“User Profile Service Application*”}

Update-SPRepopulateMicroblogLMTCache -ProfileServiceApplicationProxy $proxy

[System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.Office.Server”)

[System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.Office.Server.UserProfiles”)

$url
=
http://mysiteurl.domain.com

$contextWeb
=
New-Object
Microsoft.SharePoint.SPSite($url);

$ServerContext
= [Microsoft.Office.Server.ServerContext]::GetContext($contextWeb);

$UserProfileManager
=
New-Object
Microsoft.Office.Server.UserProfiles.UserProfileManager($ServerContext);

$Profiles
=
$UserProfileManager.GetEnumerator();

foreach ($oUser
in
$Profiles ) {


if ($oUser.item(“SPS-PersonalSiteCapabilities”).Value -eq 14 ){


$personalurl
=
$url
+
$oUser.item(“personalspace”).Value


Write-Host
$oUser.item(“AccountName”).Value

Update-SPRepopulateMicroblogFeedCache -ProfileServiceApplicationProxy $proxy -accountname $oUser.item(“AccountName”).Value


#-siteurl $personalurl

}

}

$contextWeb.Dispose()

After running the script on each WFE where distributed cache runs, wait 15 minutes for the Newsfeed data to populate.

Then test newsfeed.

References

http://consulting.risualblogs.com/blog/2014/04/01/export-impor-distributed-cache-configuration-in-sharepoint-2013/

http://sharepoint.stackexchange.com/questions/125798/userprofileapplicationnotavailableexception-logging-userprofileapplicationpro

http://netwovenblogs.com/2014/03/11/the-newsfeed-is-not-working-on-mysite-in-sharepoint-2013/

SharePoint 2013, IIS7, NLB, SSL certificates and GoDaddy Renewal Steps

Overview:

SSL certificates with SharePoint 2013 web applications expire, and when that does, you have to generate a new SSL Certificate. In this post, I will go over how to renew you SharePoint 2013 SSL HTTPS website with GoDaddy, even including multi-server Web Front End (WFE’s) topologies. If you use wildcard certificates on you SharePoint websites, there are a few gotchas when renewing. The process is similar for most certificate types, but wildcards and SharePoint are this blog posts focus. These steps are also similar if you are adding a SSL certificate to your website for the first time (once your SharePoint farm, web applications, and site collections have been configured to use HTTPS, etc.).

Here is an overview of the steps involved with the certificate renew process:

  1. Request a new certificate request from the machine running IIS/SharePoint (Pick a WFE)
  2. Go to GoDaddy and rekey your certificate, entering your certificate request text from step 1
  3. Complete the certificate request in IIS on WFE
  4. Update WFE bindings to use SSL cert
  5. Export certificate from WFE to WFE2 (PFX with personal information, create a password)
  6. Import the PFX on WFE2 IIS
  7. Update WFE2 bindings to use SSL cert

Common issues:

First, this is my experience. Comment below any corrections or other helpful information.

  • When adding the cert to IIS and refreshing, it disappears!
    • Your certificate request is expired. Generate a new one and try again.
    • You are following GoDaddys guide, which does not work. Follow my post below.
    • The cert might already exist and need to be deleted in the Certificate Manager on the server.
  • CER, CRT, PFX- what is the difference? Why do I have to select *.* if I need a specific type? Who designed this stuff…
    • CER is a request
    • CRT is a certificate without private information
    • PFX is a certificate package with private information (exported from CRT paired on the first server, the PFX is imported to the second server).
  • How do I complete a request on WFE2 if it was already completed from WFE1?
    • Export the working cert from Server 1 as a PFX file with a password, then import it on server 2 in IIS. Do not use cert manager on server 2.

Steps to renew your Existing wildcard SSL Certificate:

  1. Verify your certificate is expired by navigating to your SharePoint site. If you get an HTTPS trust warning, it’s expired or has issues that this blog post will address.
  2. Go to WFE1 IIS 7 on your SharePoint box
    1. Go to Server Certificates in IIS

    2. Remove any old certificates that contain the URL for your SharePoint site that we are renewing

    3. On the top right in IIS, go to “Create Certificate Request”

    4. Enter your information. Common name is the wildcard URL. The rest, do not use abbreviations. See this post for more info: https://support.godaddy.com/help/article/4800/generating-iis-7-csrs-certificate-signing-requests

    5. Select “4096” for the bit length

    6. Select a location/filename for the text file that is about to be generated

    7. We will be copying the contents of this file to GoDaddy to rekey our wildcard SSL certificate in the next step.
  3. Now that we have our server “key” information waiting in the text file, we can now go to GoDaddy and pair this server information to that of our SSL certificate.
    1. Go to Go Daddy Certificate Manager (Manage SSL Certificates > Manage Certificates)

    2. Select “Re-Key” on the top navigation
    3. Paste your text file contents from the IIS text file to this GoDaddy window:

    4. Select “Re-Key”
    5. Click “Manage Certificates” From the top navigation, then select “Certificates” folder on the left navigation.
    6. Select the bottom SSL certificate (the most recent version)
    7. Select “Download” icon from the navigation.

    8. Select IIS7, the “Download”

    9. Save this zip to your WFE server where you created the IIS certificate request.
    10. Extract to C:\Temp and proceed carefully to the next steps in this post.
  4. On WFE1 in IIS where you created the certificate request, open IIS 7 and follow these steps to use the certificate you downloaded from GoDaddy.
    1. Remove any old expired wildcard certificates from the WFE1 servers “Certificate Manager”, check Personal > Certificates and the Intermediate > Certificates locations

    2. COMMON GOTCHA: Do not install the cert, do it using IIS.
    3. Go back to “Server Manager” in IIS 7, select “Complete Certificate Request” on the right navigation

    4. Enter the information for the Certificate request as follows:

    5. COMMON GOTCHA: Select *.* when browsing for the CRT file from the GoDaddy zip

    6. Friendly name must be the wildcard URL of the domain.
    7. Click OK.
    8. Refresh the Server Manager to verify the certificate “stays”. If it disappears, you either have:
      1. A certificate in your Personal Certificate store with the same friendly name
      2. An expired or old Certificate Request you generated and downloaded, or you downloaded an older certificate from GoDaddy. Repeat these steps and it will work (it should).
  5. Set the IIS binding of the new certificate to your SharePoint 443 SSL HTTPS website in IIS:
    1. Go to IIS 7 > Sites > select the SharePoint site that uses the wildcard cert.
    2. Select “Bindings” on the right with the website selected.

       

    3. Select “Edit” and select the new SSL certificate

    4. Select OK. On WFE2, you will get an error here trying to use an exported PFX file, follow the next steps to fix WFE2.
    5. Verify the site loads on WFE1 if you can control your DNS/NLB routing.
  6. If you have additional WFE servers, you need to export this new verified SSL certificate to IIS. Here is how.
    1. From WFE1, Go to “Server Certificates”, right click the wildcard cert and select “Export”

    2. Pick a location for the new PFX file, then enter a secure password.

    3. Click OK
    4. Copy the PFX file to WFE2 through Explorer or any other method.
    5. On WFE2, go to IIS 7 > “Server Certificates” and select “Import”

       

       

       

    6. Browse to the PFX file copied over from WFE1, enter your password and select OK.
    7. Refresh “Server Certificates” to verify it is still available.
    8. Repeat the import process in IIS on other WFE servers.
  7. Now that the certificate is available on the other WFE’s in IIS, we need to update the bindings. Same process as the first WFE.
    1. (Copied and pasted from WFE1 steps, but perform these on the WFE2 and additional servers once the certificate is imported)
    2. Go to IIS 7 > Sites > select the SharePoint site that uses the wildcard cert.
    3. Select “Bindings” on the right with the website selected.

       

    4. Select “Edit” and select the new SSL certificate

    5. Select OK.
    6. Verify the site loads on WFE2 if you can control your DNS/NLB routing.

That’s it! I believe most of what’s above is best practices. I would also remove temporary certificate files, such as PFX, CSR files, etc. left around during the process for added security.

Managed Metadata field error on SharePoint 2013 “The given guid does not exist in the term store”

We recently migrated our SharePoint 2010 content database to SharePoint 2013.

In SharePoint 2013, users tried to use a MMD field/column on a new item form and received this error: “The given guid does not exist in the term store”

After researching the issue, it appears that the error is due to a disconnect between the list column and the MMD values. I remembered the MMD database/terms for the Managed Metadata Service Application is separate from the content database.

I opened the Managed Metadata Service Application on SharePoint 2013 and confirmed there were no terms present:

Optional: To confirm the term store structure back on SharePoint 2010, I opened the Managed Metadata Service Application on the SharePoint 2010 farm, the terms were indeed present:

Since we only migrated the SharePoint 2010 content database (not the Managed Metadata Service Application data), the MMD field fails to retrieve the values from the new SharePoint 2013 farm.

Solution: You will have to migrate the Managed Metadata Service Application from SharePoint 2010 to SharePoint 2013.

For more information on how to upgrade the Service Applications, see http://technet.microsoft.com/en-us/library/jj839719.aspx

“When you upgrade from SharePoint 2010 Products to SharePoint 2013, you must use a database attach upgrade, which means that you upgrade only the content for your environment and not the configuration settings. After you have configured the SharePoint 2013 environment, and copied the content and service application databases, you can upgrade the service applications to SharePoint 2013. This article contains the steps that you take to upgrade the service applications.”

SharePoint Designer – CSS Caching issues with Visual Studio Web Part packages

I have recently ran into an issue with SharePoint Designer and modifying a CSS file. The CSS file would not update my changes.

I waited a 1/2 a day, checked different browsers, cleared my temp/etc, tried virtual machines that havent seen the website in months. Nothing was reflecting my SharePoint Designer CSS changes. I was also logged in as the same user, and I checked to make sure that the CSS file was being referenced, and no others were overwritting it.

What could it be? Well, after confirming that even deleting the file would not change any of the CSS, I worked with my co-worker about server-side caching. These CSS files deployed Via web part are located in the 12 hive. When you edit them in SPD, the file is a ghosted version. After you publish the file, you can Reset to Site Definition. We checked the Blobcache settings via server side and noticed that the caching was set to 24 hours. Another CSS file deployed via web part was ghosted, and the changes would reflect real time as I saved. Really strange behavior.

I will keep looking into this and post a solution.