If anyone asks is Microsoft still investing in SharePoint - show them this.

I was attending an MVP community event, and a few non-office MVPs asked me, hey is Microsoft still investing in SharePoint?  Is there anything new coming next?  There hasn't been a product for a few years right?  (This is so odd, we just had SP2016...  and SPO hasn't stopped having new features every month.)

I was also working at a client when one of our advanced users rushed over and asked John - what's going on with this new Teams thing - is Microsoft scrapping SharePoint.  (I was pretty shocked at this one).

Realizing that I'm probably directly connected to the flood of news regarding SharePoint - it is surprising people aren't aware of many of the best and greatest of SharePoint is still to come.  I decided to write this post.

April - SP Tech Con (lastest info as of this post)

This is the latest info as of this post.  Follow Mark Kashman - PM SharePoint. 

This was the SPTechCon keynote. 
https://www.slideshare.net/markkashman/reinventing-content-collaboration-the-future-of-sharepoint-is-now-sptechcon-austin-ms-keynote

 

Feburary - Internal Demo Day

Is there still a SharePoint team?  YES - and they are legion.

Directly from Jeff Teper the father of SharePoint

May - What's next?

2017 May 16 is the SharePoint Virtual Summit.  This is the big one.  Especially if we look back to 2016's May the 4th event when we first embarked on the next step of the Future of SharePoint, and now we see what the team has delivered within just one year.  It's been nothing but awesome. 

So no matter where you are in the world - you should register for the Virtual Summit.

In Australia

The Office 365 Saturday is upon us really soon.  The annual free community event will be coming to a city near you!  You need to sign up.  This helps us plan our catering needs.

March PnP special interest group call and Azure Functions demos

On March 22, I put my hand up to do demos for the Office Patterns and Practices Special Interest Group conference call.  We talked about Azure Functions with PnP, and Bert talked about a new Modern Page scanner that can be used to check if sites have customizations that would make migrating to modern pages difficult.

PnP Core, PowerShell and Provisioning Engine SIG recording from March 22nd, 2017. Details around the covered content and direct links to demo section from TBD

I had a lot of fun.  It probably was very obvious I was laughing most of the way.  This is a follow up post of various things that I had noted down but unsaid.

Demo files

All my Functions in the demo are published to: https://github.com/johnnliu/azure-functions-o365 

That Get-AccessToken typo

It turns out the problem is the dash - character that I copied from GitHub.  If I retrype the command as - (dash) then the cmdlet is fine.

Costs

Vesa mentioned this at the end.  Azure Function's pricing is very cheap.  It is actually because Functions don't run on VMs provisioned for you.  Azure finds an unused VM, copy your function on it, then run it, and then delete it.  The free monthly compute bucket is 400,000 Gb/s.  If you exceed the limit it is still dirt cheap.  I'm serious about the fact that I have turned off all my VMs, I only run Functions.

Also, I'm just as shocked as you are that Vesa seems to know exactly what I'm about to present next.

Docker

My platform as a service goes up.  I see Docker as going downwards and not the direction I want to go.  Functions as a Serverless platform needs to be above Web Server as a platform.  They could all live on top of Docker, but that's an implementation detail that I don't need to care about.

Configure Inputs and Outputs

Because Functions are serverless, it is important to understand that you don't really want to even write your own code for handling output.  If you want to write to Blob Storage or Azure Queue - there are output configuration you can set up.  Then the output from the function is written directly to those sources without you having to write bare minimum code.

Configuring Inputs also allows you to switch from a HttpTrigger to a TimerTrigger, if you need that.

Flow to Azure Functions

If you feel uncomfortable with having an open public URL exposed - you can have Flow upload a JSON directly to an Azure Queue, then execute the Azure Function from the queue.  This also provides automatic-retries.

Future announcement - PnP SIG JS Call

I've done a bit more work with pnp-js-core, azure-functions-cli and azure-functions-pack.  And have put up my hands to Patrick to do a PnP-JS-Core SIG call in the future.  Need a few pieces to sort out first.

Azure Functions has a very definite place with NodeJS as well.  We'll explore that hopefully soon.

Simple custom Angular (Angular2) Pipe that extends Number

We have a lot of code that looks like this in our new Angular (Angular2) module:

<div style="width:210px">
    <div>
        {{ (overviewDetails.TotalTurnoverActualSecured ?
      (overviewDetails.TotalTurnoverActualSecured / 1000000) : 0) | number:'1.2-2' }}
    </div>
    <div>{{ overviewDetails.TotalTurnoverActualSP / 1000000 
      | number:'1.2-2' }}</div>
    <div>{{ overviewDetails.TotalTurnoverActualSPF / 1000000 
      | number:'1.2-2' }}</div>
    <div style="clear:both"></div>
</div>

The main reason is that we just want to display numbers in 1.2 million, not 1200,000.

So obviously, we think about wrapping this in a custom Pipe function.

number is a Pipe name for the DecimalPipe class.  So we can extend that and borrow all the work the DecimalPipe does with parsing and formatting.

import { Pipe } from '@angular/core';
import { DecimalPipe } from '@angular/common';

@Pipe({
  name: 'million'
})
export class MillionPipe extends DecimalPipe {
  transform(value: number): any {
    return super.transform(((value || 0)/ 1000000), "1.2-2");  
  }
}

The Angular2 template then simplifies down to:

<div style="width:210px">
    <div>{{ overviewDetails.TotalTurnoverActualSecured | million }}</div>
    <div>{{ overviewDetails.TotalTurnoverActualSP  | million }}</div>
    <div>{{ overviewDetails.TotalTurnoverActualSPF  | million }}</div>
    <div style="clear:both"></div>
</div>

It is so nice to work with a Proper Object-Oriented language like TypeScript here.  Look at that extends.  <3

 

O365 Customizations in the year 2017

It is the year 2017 - watch out, because O365 customizations are full speed ahead.

This is a MS Australia Ignite 'lightning talk' for Meetup Madness (sorry, I took twice the amount of time than I should) to convince a room full of people that loved O365 to become developers.

MS AU Ignite Meetup Madness - O365 Customizations in 2017

Much laughter was had.  But I stand by what I said - you are now all developers, now go build something amazing!

 

AzureFunctions, PowerShell, MS Graph and AppOnly permission

In a previous blog post, I wrote about connecting to Microsoft Graph with Resource Owner grant.

That particular authentication scheme is for delegate permissions.  So the function, utilizing an account's username/password, is performing actions as that user.

I'm revisiting that post today and instead talk about App-Only Permissions, and the steps are very similar, and to some may be easier.

 

Register App-Only

Head into the New Azure Portal.

Add "Read and write all groups" permission under "Application Permissions"

You should see this:

"1 Application Permission"

Hit the Grant Permission button here.

And set up a Client Secret

Nearly there, one more step we need to make the registered app allow implicit flow.

Hit the Manifest button, change oauth2AllowEmplicitFlow property to true, hit save.

Copy the Application ID - this is the Client ID.

On the first page of Azure AD properties, grab the Directory ID

 

 

You now have:

  • Directory ID (Tenant ID)
  • Application ID (Client ID)
  • Key (Client Secret)
  • And consent for your app has been granted.

 

PowerShell Code

 

$tenant_id = "26e65220-5561-46ef-9783-ce5f20489241";
$client_id = "44da3f20-fc0b-4f90-8bb1-54b1d50e3ecf";
$client_secret = $env:CS2;
$resource = "https://graph.microsoft.com";

$authority = "https://login.microsoftonline.com/$tenant_id";
$tokenEndpointUri = "$authority/oauth2/token";
$content = "grant_type=client_credentials&client_id=$client_id
            &client_secret=$client_secret&resource=$resource";

$response = Invoke-WebRequest `
    -Uri $tokenEndpointUri `
    -Body $content `
    -Method Post `
    -UseBasicParsing

 

The small difference between this time and the previous blog is now we are using AppOnly permissions.  The grant_type is client_credentials.  And you don't need to supply username/password.  The clientid/clientsecret pair is sufficient.

 

The whole thing

$name = "group-name-here"

$client_id = "44da3f20-fc0b-4f90-8bb1-54b1d50e3ecf";
$client_secret = $env:CS2;
$tenant_id = "26e65220-5561-46ef-9783-ce5f20489241";
$resource = "https://graph.microsoft.com";

$authority = "https://login.microsoftonline.com/$tenant_id";
$tokenEndpointUri = "$authority/oauth2/token";
$content = "grant_type=client_credentials&client_id=$client_id&client_secret=$client_secret&resource=$resource";

$response = Invoke-WebRequest -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing
$responseBody = $response.Content | ConvertFrom-JSON
$responseBody
$access_token = $responseBody.access_token

# GET https://graph.microsoft.io/en-us/docs/api-reference/v1.0/api/group_list
# $body = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/groups" -Headers @{"Authorization" = "Bearer $access_token"}
# $body | ConvertTo-JSON

# POST - this creates groups https://graph.microsoft.io/en-us/docs/api-reference/v1.0/api/group_post_groups
$body = @{"displayName"= $name; "mailEnabled"=$false; "groupTypes"=@("Unified"); "securityEnabled"=$false; "mailNickname"=$name } | ConvertTo-Json 
$body = Invoke-RestMethod `
    -Uri "https://graph.microsoft.com/v1.0/groups" `
    -Headers @{"Authorization" = "Bearer $access_token"} `
    -Body $body `
    -ContentType "application/json" `
    -Method POST
$body | ConvertTo-JSON

There are no other dependencies - authenticate and calling MS Graph is really that simple - two web requests!

 

The default "Owner"

Usually, the user that created the group becomes the Owner of the group.

In the Delegate Permission example in the last blog, the group is created with my user account and I'm the owner.

In the Application-Only Permission example in this blog, the group is created with the app account and there is no user owner!