Experimenting with SharePoint Hub Sites

A colleague (@spbrusso) and I have been playing with hub sites for the last few days.  We noticed some quirky things and started digging around and this is what we found.


When you create a hub site, a directory is created under _Catalogs and it’s appropriately called “hubsite”.

hubsite catalog

In that folder is a json file containing your new nav bar details which include the logo and urls.  This nav bar doesn’t appear to automatically pick up joined sites.  You must manually manage your navigation from the hub site.

hub site nav

Here is a sample of that json file.


  "themeKey": null,

  "name": "Hub Site",

  "url": "https://.sharepoint.com/sites/HubSite2",

  "logoUrl": null,

  "usesMetadataNavigation": false,

  "navigation": [


      "Id": 2003,

      "Title": "Team Two",

      "Url": "https://.sharepoint.com/sites/teamtwo",

      "IsDocLib": false,

      "IsExternal": false,

      "ParentId": 1002,

      "ListTemplateType": 0,

      "Children": [ ]




Associated Sites

The top navigation doesn’t propagate immediately and isn’t controlled by the normal Navigation settings that we’ve used in the past.  My guess is that there is a timer job running in the background that updates the top navigation for associated sites.  When it runs, the json file at the hub is copied to the same location at the associated site.  If you change the navigation at the hub, know that you may need to wait an hour or so for the associated sites to show the same top nav.  (I didn’t time it, but I believe I waited 45-60 minutes before I saw a change).  You can’t edit the top nav from the associated site; you have to do it from the hub.

If you crack open the hub site with the SharePoint Client Browser tool, you’ll see the navigation node with the links that you’ve created.  This is not the case on the associated sites so I’m thinking that the process is that a user creates the nav, which is stored in that Top Nav Bar, the items get copied to the file, the file gets copied to the associated sites, and the associated sites read from their copies.

hub site top nav


When you first create a modern team site, not associated with a hub site yet, you will see a random theme applied.  If you leave the default theme on the hub site and then associate sites to it, they appear to keep their own theme.

If you set a theme at the hub and then associate a site to it, that site will instantly pick up the theme.

If you change the hub site’s theme, the associated sites won’t pick up the change until you disassociate them from the hub and re-associate.  I tried changing the top navigation and waiting to see if the theme would change when the nav changes were propagated but they didn’t.

Update: If you change the hub site’s theme, the associated sites may take 2 or more hours to pick up the theme change.  





Office 365, the Microsoft Bot Framework, & Cognitive Services: 50,000 Foot View

Over the summer, I started working with the Microsoft Bot Framework for three reasons:

  1. Bots are a cool technology that has become mainstream
  2. It provides awesome new possibilities in terms of productivity and customer engagement
  3. The company that I work for is making a clear push toward AI and Machine Learning

I’ve been sitting on this blog post for a while because I didn’t want to regurgitate the information that I was sourcing from, then I landed a project that kept me busy, then I agreed to do a presentation on the topic, and finally, I ended up having to build a proof of concept with it.  NOW… I have some time to talk about it.

Building a bot is not overly complicated but building a bot is a little underwhelming if that’s all you do with it.  Integrating artificial intelligence, to me, feels like a must because it provides that next level of interaction that makes it worth while.  AI is what will make your bot feel like you’re not interacting with a bot… at least until it tells you that it doesn’t understand what you’re asking when you say “hello”, but we just need to account for those things.

This post will try not to be a repeat of info that is readily available.  Instead, I’ll walk you through a bot that I built which was a help desk triage bot.  It’s a pure proof of concept that I wouldn’t necessarily put in a production environment, but it shows you what’s possible.

The bot, which I named ANA is made up of 3 major components.

  • First, (and obviously) is the Microsoft Bot Framework which will handle the message handling.
  • Second, I leveraged Microsoft’s Cognitive Services (specifically the Language Understanding Intelligent Service or LUIS) to provide a simple level of interaction.
  • Third, I used the Microsoft Graph to further integrate it into the Office 365 ecosystem.

Setting up ANA

I’ll go over the steps I took to set my bot up but there are a few other resources available for getting started so I won’t do my normal step by step.

First, you can visit the Bot Framework Documentation site which has plenty of good information on getting started.  Really check this out, the bot framework isn’t a simple message handling framework.  It has some cool features like the ability to enter “3 days from now” and it knowing how to parse that text to assign a date value equivalent to 3 days after the current date.

Another source that I saw recently was by @zimmergren (Tobias Zimmergen) who recently did a fairly comprehensive post on building a bot via the Azure Bot Service. I used the steps for creating the bot using the .NET SDK.  I haven’t tried the Azure Bot Service steps as of yet so I can’t recommend an option.


In order to create the bot, you will need Visual Studio 2017.  I tried using VS2015 (because that’s what I had at the time) but the Bot Application template wasn’t available for it.  The nuget package can be found here and here is the Bot Template for Visual Studio.

If you need anything else, you’ll likely find the relevant resources in the Start Building Bots page.

You’ll also want to install the BotFramework-Emulator so that you could connect to your bot without having to deploy.  For information about the emulator, visit the Debug bots with the Bot Framework Emulator page.  You can find the emulator setup file on github.

The Project Setup

Create a new Bot Application project in Visual Studio 2017


Solution Structure

When you create your new Bot Application, you’ll get 2 classes.  The Message Controller and a Root Dialog.  The Dialogs are meant to be a way to compartmentalize your various types of communications.  Your message controller takes the message received by the user and determines what to do with it.  When you create a new project, it’s setup to send it to the RootDialog which does the simple task of counting the number of characters in your message.

public async Task Post([FromBody]Activity activity)
if (activity.Type == ActivityTypes.Message)
//await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
await Conversation.SendAsync(activity, () => MakeDialog());
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;

Your web.config file also has 3 app settings that you’ll need when it’s time to deploy.

<!-- update these with your BotId, Microsoft App Id and your Microsoft App Password-->
<add key="BotId" value="YourBotId" />
<add key="MicrosoftAppId" value="" />
<add key="MicrosoftAppPassword" value="" />

Integrating the Language Understanding Intelligent Service (LUIS)

At this point, you have what amounts to as a Hello, World bot where you can see how things plug into each other.  I suggest you go over the Bot Framework documentation to get the details around creating dialogs and forms.  As you build your bot, you’ll find other things that you may want to incorporate.  For example, you may want to provide a Describe or Prompt attribute to change the way the Bot asks users for input.

If you want your bot to have some intelligence, you’ll want to start incorporating the Cognitive Services.  The Cognitive Services are so cool in my opinion.  They’re fairly simple to use and offer so much potential.

A very high level description of how they work is you feed it some input which will be different depending on which service you use and it returns a score.  That score is used to make ‘predictions’.  I know… that was maybe a bit too high level.  Let’s take a look at how I used it.

Ana has a few dialogs.  Some that I incorporated from demos, others that I created myself once I got the hang of it.  Two of those dialogs are the Greeting dialog and the HelpDesk dialog.

The Greeting dialog will receive a message like ‘hi’ or ‘hello’ and respond with a greeting of its own.  The Help Desk dialog was one that I developed once I got the hang of things.  Its job is to ask you questions about the kind of help desk support that you require and it takes that information and creates a task in Planner.  Again, we’re living in proof of concept land so this wasn’t intended for practical application.

Given that my solution accepts multiple dialogs or multiple types of conversations, a greeting and a help desk request, I need to be able to differentiate “hello” from “i need to submit a help desk ticket” and any variation of that.  This is where the Cognitive Services come in!

In the image below, you’ll see the list of Intents.  So when someone communicates with my bot, my bot will expect the intention to be a Greeting, a Help Desk request, or None which is where unknown requests go.  When this one is hit, that’s when you’re bot will throw it’s hands up… if it had hands.

LUIS Intents

Inside an intent is what is called an Utterance.  An utterance is the sentence that is associated with the intent and what the probability is that the sentence was intended to be.  If we look at the Greeting intent, we can see that there are 2 utterances associated with it.  ‘Hi’ has an 80% chance to be a greeting.  ‘Howdy’ has a 70% chance.

greeting utterance.PNG

Similarly, our help desk ticket has it’s own list of utterances.  In this case “I’d like to submit a help desk ticket” and “I’d like to submit a ticket” are both 100% intended to trigger my help desk ticket logic.

help desk utternance

The Suggested Utterances tab also stores a list of utterances that the service had no idea what to do with.  You can go in there and set the intent on the utterances so that those same words/sentences can also trigger the appropriate logic.

help desk suggested utterance

There’s more to it than just this but I highly recommend you read up on the Cognitive Services.  The site has some cool demos and there are other types of services like image recognition services and others.

LUIS and ANA Start Communicating

The following code starts with a couple of attributes.  LuisModel takes 2 guids and the text below shows you where you can get those.  The 2nd attribute is Serializable which is needed by the bot framework.

There are 3 methods which are also decorated with LuisIntent attributes.  The first has a blank value and this will be something that is caught in the None intent seen in the previous images.  The second intent is the Greeting intent.  This Greeting method will be called if the user enters Hi or Howdy as we saw in the Greeting intent.  Finally, we have the Help Desk Ticket, which is called… yep, you guessed it… when the Help Desk Intent is triggered.  There is also a HelpDeskTicket class that contains my the logic for submitting a ticket which uses the Microsoft Graph to create a task in Planner.

[LuisModel("<APP ID from LUIS Dashboard page>", "<key string from LUIS Publish App page>")]
public class LuisDialog : LuisDialog<object>

internal BuildFormDelegate<HelpDeskTicket> Ticket;

public LuisDialog()


public async Task None(IDialogContext context, LuisResult result)
await context.PostAsync("I'm sorry, I don't know what you mean.");

public async Task Greeting(IDialogContext context, LuisResult result)
context.Call(new GreetingDialog(), Callback);
public async Task Callback(IDialogContext context, IAwaitable<object> result)

public async Task HelpDeskTicket(IDialogContext context, LuisResult result)
if (this.Ticket is null)
this.Ticket = Models.HelpDeskTicket.BuildForm;
var helpDeskForm = new FormDialog<HelpDeskTicket>(new Models.HelpDeskTicket(), this.Ticket, FormOptions.PromptInStart);
context.Call<HelpDeskTicket>(helpDeskForm, Callback);


Data Access with the Microsoft Graph

The HelpDeskTicket class is where I kept my form logic.  Its fairly direct.  First it collects your help desk ticket information, then it authenticates, creates a Planner task object and assigns its values based on the user’s response, then it posts the task.

namespace Bots.Ana.Models
public enum RequiredService


public enum Severity

public class HelpDeskTicket
// the order of the variables determines the order of the questions
public RequiredService ServiceRequired;
public Severity IssueSeverity;
[Prompt("Tell me about your issue.")]
public String Description;

public static IForm<HelpDeskTicket> BuildForm()
// when the form is completed, run the submission logic
OnCompletionAsyncDelegate<HelpDeskTicket> submission = async (context, state) =>
await SubmitTicket(context, state);

return new FormBuilder<HelpDeskTicket>()
.Message("Welcome to the help desk ticketing bot!")

private static async Task SubmitTicket(IDialogContext context, HelpDeskTicket state)
string token = string.Empty;
context.UserData.TryGetValue("AccessToken", out token);

GraphServiceClient graphClient = SDKHelper.GetAuthenticatedClient(token);

GraphService service = new GraphService(context);
var me = await service.GetCurrentUser();

PlannerTask task = new PlannerTask();

task.Title = $"{state.ServiceRequired} issue reported by {me.DisplayName}";
task.PercentComplete = 0;
task.PlanId = ConfigurationManager.AppSettings["HelpDeskPlanner"].ToString();

// get the guid for the Planner Bucket from the web.config that matches the severity chosen
switch (state.IssueSeverity)
case Severity.High:
task.BucketId = ConfigurationManager.AppSettings["HighPriority"].ToString();
case Severity.Medium:
task.BucketId = ConfigurationManager.AppSettings["MediumPriority"].ToString();
case Severity.Low:
task.BucketId = ConfigurationManager.AppSettings["LowPriority"].ToString();
case Severity.Emergency:
task.BucketId = ConfigurationManager.AppSettings["HighPriority"].ToString();
task.BucketId = ConfigurationManager.AppSettings["NoPriority"].ToString();

PlannerTask planTask = await service.CreatePlan(task);

await context.PostAsync($"Your ticket has been submitted.  A technician will contact you soon.");


That was your 50,000 ft view of the application. I recently presented it at a user group where I went over the various integration points in Office 365. I showed the Bot running in Microsoft Teams and Skype. Here’s what the bot looks like in action.

First, a look at Microsoft Teams.

ana teams

Next, a look at Skype

ana skype

SharePoint Framework – Package and Deploy your Solution

In the previous 2 posts, I walked you through the setup of several required packages, talked about the SharePoint Workbench, and provided a script to create a public CDN in SharePoint Online.  I also avoided showing the solution.  The code itself isn’t important, but the structure is, so let’s take a look at that while we walk through bundling, packaging, and deploying.

The files that are important to packaging and deploying can be found in the config folder.


Bundle & Deploy

You’ll need to specify a cdn base path for the solution; otherwise, you’ll see that the webpart will try to reference files from the localhost instead of the destination location.  (In a previous post, I provide a script to create a CDN in SharePoint Online).  If you have a CDN in place, open the write-manifests.json file and provide the URL.


You can bundle the solution files with the following command:

gulp bundle --ship

Once you run the previous command, you’ll see a new Deploy folder was created inside temp. You’ll want to upload the files from the Deploy folder to the CDN that you’ve specified.
temp folder

So now you’ve told the solution where it’s going to get it’s content files and it’s time to deploy.

The solution package for an SPFx solution uses yet another new extension (sppkg).  In order to create the package, we invoke the package-solution.json. The file will contain a path where your solution will be deployed. In this case, my solution will be added to “SharePoint/Solution” and will be given the name sp-fx.sppkg.

To create that package, you’ll need the following PowerShell command:

gulp package-solution --ship

Next, take your sppkg file and upload it to your app catalog. You’ll notice that the CDN path appears in the modal to show you where the content for that solution will be referenced from.


Note: If you omit the “–ship” from the powershell commands above, you’ll create packages for debugging and not production. You’ll know that you did this if you see localhost as the content location instead of your CDN.

Now, your solution has been deployed and you’ll just need to add it. If you go to Site Contents > Add an App, you’ll find your web part. Finally, you can add your web part to the page the same way you’re used to adding web parts.Add a webpart

… And that’s pretty much it.

Hello, SharePoint Framework (SPFx): Getting Started – Quick and Dirty

I know, I know… there’s github, pluralsight, PnP, dev.office.com and a plethora of videos and sites where you can find info on getting started with SPFx.  We’ll just add this one to the list.  This is a quick and dirty getting started guide.  I’ll also provide some references for you to dive deeply into related topics.


What do I need to install?

For starters, you need to install Node.js. Get yourself the LTS version.  nodejs

Once you have that installed, you can open up a command shell of your choice.  I’ll be using PowerShell.  You’re going to want to install the node package manager (npm) globally so you can use it from the command line for all of your projects.  In order to do that, you’ll just need to run the following command in PowerShell or whatever command shell you decided to use:

npm install npm -g

If the above powershell looks a little weird to you, what we’re saying in english is that we want to use the node package manager to install the latest node package manager globally.

Next up is the Yeoman Generator.  This is a handy little tool that creates your SharePoint project for you.  So we need to go back to your trusty npm and install yeoman globally.

 npm install yo -g 

Once you have the Yeoman generator, you’ll want the SharePoint template generator.

 npm install @microsoft/generator-sharepoint -g 

Next up, Gulp.  Now, gulp is a task automation tool.  We’re basically going to use it to run our solution.

 npm install gulp -g 

There’s one last package that needs to be installed and that’s webpack, but I’ll skip it for now.  We’ll set that up when it’s time to deploy our solution.

First Web Part

Now that we have (almost) everything that we need installed, we can start with our project.  The installs above were 1 time installs since we specified that they were each to be globally installed.

The first thing that we want to do is create the directory where our solution will live.  So in PowerShell, I go to the base directory where I store all of my solutions and then I type the following command to create a directory called “SPFx” (call it whatever you want):

 md SPFx 

The above is the mkdir command so you could type the following and it’s exactly the same:

 mkdir SPFx 

Then we want to go to that newly created directory and run our Yeoman generator to create the project files. (At this point, you should be in your project path in PowerShell)

 yo @microsoft/sharepoint

Next, the generator will start asking you for details like what do you want to call your solution, are you building a web part or an extension, names, descriptions, and what JavaScript library you want to use. (The version of the generator at the time of this post is Version Depending on which version you’re on, you may need to provide different inputs.

Here are my inputs. Once I hit enter, it starts installing a ton of stuff so you have time to check your email or maybe get a beverage of your choice.
sp yeoman

And this is what you’ll see when it’s done.  If you look inside the box in the image, it tells you what you need to “start” your solution.  You can go look at the 252 MB of SharePointy goodness that get’s created in the directory above.  (The size on disk is 297 MB).  I’ll let you inspect the files but for our purposes, we just want to run it.  This part I find kind of cool.  You DON’T need SharePoint installed on your machine for this.

sp yeoman end

Type the following to launch your solution inside the SharePoint Workbench!

 gulp serve 

There are a few things to note here.
First, this isn’t SharePoint but it functions like it in that you can click on the plus symbol to add a new web part. You’ll also see a few buttons at the top that will simulate what the page will look like on a mobile device or tablet.


Also, you may notice a certificate error and if you pay attention to the last bit of text outputted by the gulp serve command, you’ll see a warning regarding this. The workbench is using https and won’t load your scripts so you’ll want to run 1 more gulp command which we’ll do next, but first, I’ll add my web part to the page.

hello world workbench.PNG

Run the next command to fix the cert issue. The command will install a certificate. You’ll also get a prompt to confirm and the next time you run gulp serve, you won’t see the cert issue.

 gulp trust-dev-cert 

There’s something pretty cool about the workbench. If you have it running, you can go to your SharePoint Online and test out the page from there. Just go to your site and append /_layouts/15/workbench.aspx.  (Example: https://<tenant&gt;.sharepoint.com/_layouts/15/workbench.aspx)

That’s what you need to get started. To recap, we ran a few 1 time installs (nodejs, npm, gulp, yeoman, and the SharePoint generator). We created a solution using the yeoman generator, and we took a look at the SharePoint Workbench.

This was meant to be a quick and dirty starter guide (mainly for me). Below are additional resources if you want to start diving deeper into some of the concepts mentioned above. In another post, I’ll talk about webpack and deploy a solution.

Additional Resources

Error occurred in deployment step ‘Activate Features’

I was recently working on a solution that consisted of custom content types and forms.  I worked on it in my dev environment, made sure it was good to go, and tried to deploy it to a shaky test environment.  When deploying via PowerShell, the deployment status showed “Not Deployed” while the last operation result showed “The solution was successfully deployed”.


Everything that needed to go into the hive was where it should be but when I went to the Site Collection Features, my Feature was not there.  That test box also had Visual Studio on it so I tried deploying with Visual Studio and got the following error:

Error occurred in deployment step ‘Activate Features’: Feature with Id ‘<guid>’ is not installed in this farm, and cannot be added to this scope.

As I mentioned earlier, that test server has some issues so I checked everything; permissions, web.config, Visual Studio settings, the ULS, Job Definitions, Services, you name it.  I even created a Empty SP2013 solution with nothing in it other than a Feature.  I got the same errors and the only times that I didn’t were when I removed the Feature from the package or set the Feature’s scope to Web Application which wouldn’t help me.

Solution:  I packaged the wsp, and deployed via PowerShell using Add-SPSolution and Install-SPSolution.  This got me back to where I started where all the files were where they belonged but the Feature wasn’t available in the Site Collection Features page.

I then ran the following PowerShell command and my feature appeared in the Site Collection Features page and I was finally able to activate it.

Install-SPFeature "MyFeature"

SPSite – FileNotFoundException

You may be writing a consoleapp that uses the SPSite object. You write something like:

SPSite site = new SPSite(“http://yourSiteHere&#8221;);

Running the code produces the following error:

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

You may need to go to your Solution properties and select Any CPU. If one doesn’t exist, you can make a copy of an existing platform to create it.

Hiding Items in the Site Actions Menu

I have a client that wanted to trim down the site actions menu to reduce clutter and confusion on their publishing site.  I originally went down the route of using

GroupId = "Text"
HideActionId = "Text"
Id = "Text"
Location = "Text">

in the element manifest but that wasn’t working.  The next attempt involved the CustomSiteAction.xml file found in the Masterpage gallery’s Editing Menu folder.

The file contains an empty Console node.  The example below shows how to remove items.  You can tell from the ChangedNodeID which item in SiteActions is being removed.  Those ID’s can be found in the v4.master or simply by viewing the page’s source.