Office 365 Saturday Perth #O365PER summary

On 22nd of May I had the pleasure of presenting O365 Saturday Perth.  In the past years, we have called this SharePoint Saturday.  We re-branded the name and this year had a mix of content from SharePoint On-Premises, SharePoint Online as well as Azure and Office 365.

People from Perth are always early risers - the room was quite packed early in the day.

Here's James Milne presenting the keynote.

I presented Introducing PhantomJS: Headless browser for SharePoint/Online.

Had lots of feedback about what people found it interesting, many people said it has potential for scenarios that they didn't think were previously possible.  I'm very keen to hear more feedback especially for different business cases.

Some of the feedback and demos will be rolled into an updated content over the year, so stay tuned.

Here are the PowerPoint and ZIP files for the presentation.

 

Nintex Work Inspired Breakfast Seminar - Sydney

I'm now a Nintex vTE (Virtual Technical Evangelist).  Continuing my company SharePoint Gurus' strong partnership with Nintex.

I had the pleasure of presenting Nintex Tips at the Work Inspired Breakfast Seminars - Sydney event on May 15.

http://www.nintex.com/company/events-webinars/2015/5-15-2015-work-inspired-breakfast-seminars-sydney

As I wasn't sure of the audience, I decided to cover the simple Nintex workflow scenarios and diving deeper into the complex - developer minded (heavy API stuff) at the end.

  • Site Workflows
  • Scheduled Workflow
  • Iterating through Query SQL
  • Recursive CAML Query
  • Regex Options in the Regular Expression Action
  • XSLT to clean up complex XML namespaces
  • And finally, pre-caching json queries in Javascript based applications (great for javascript charts that needs lots of data)

It was quite interesting that the Nintex Breakfast is targeted at existing Nintex customers and really just show casing what they can do with tools they have already got.  So there was no hard sell - just "you can already do this".  There was a bit of discussion from the tables regarding various techniques and tricks.

Timing wise, some were able to stay to end (developers) for the last few demos while many had to go after 9:30am.  Still a great experience and will look forward to more of these events in the future.

I've previously worked with Dan Stoll and he's always a great show.

Nintex Workflow - Lazy Checkin Everything Workflow

Doing quite a bit of client-side JavaScript (AngularJS in fact) and checking in and out lots of files that sit within Style Library.  At the end of the day, I just want something that will go through everything and check them all in for me.

Nintex Workflow time

  • Query List of items still checked out to me
  • Loop through list
  • Check-in
  • Bonus: Run-As-John

Query List

Create a Site Workflow, add a Query List action then set the filter.  Collect "URL Path" and "Checked Out To" fields to separate collection variables.

Important: Make sure you tick Recursive

Hit Run Now.
I want to change this part of the Query to Current User.

<Eq>
<FieldRef Name="CheckoutUser" />
<Value Type="Integer"><UserID Type="Integer" /></Value>
</Eq>

Hit the Execute button - looking good. 

Return to Configure Action.  Make the same change to the CAML query.

Loop and Check-In

Wrap up the rest of the Workflow.  After the Query List, set up a For Each action and loop through each URL in the URL Collection.

For each URL, run the Check in item Action.  Set it to check in by URL.

Insert Witty Comment.  Log an entry to History list for testing.

Laziness Complete

And done.  Workflow runs, John's files are checked in.

Bonus: Run-As-John

Sometimes, you might want to let a colleague check in your files on your behalf.  Nintex let you set up workflow within an Action Set to run as the Workflow Owner.

Summary

  • Now Everyone can check in all John's Mess!
  • Put this on a weekly schedule.

 

A Hybrid Future for On-Premises

Hybrid - In Theory

I think it is no suprise for us watching from the SharePoint world (sometimes with a slight envy) at all the investments in the cloud.

Microsoft makes no secret about this - cloud is a massive growth area and an area that Microsoft is and will aggressively pursue.

SharePoint itself is a product born On-Premise. But many of the Experiences are now born-in-the-Cloud.

What I was very relieved to see though, is that in this mad push for Cloud-First, Microsoft reaffirms that they will not leave their customers behind. This is where I feel the Hybrid story that has came out is so refreshing.

What's coming down?

  • OneDrive for Business coming to SharePoint 2010
  • Delve coming to SharePoint 2013 first
  • Continue to evolve Hybrid Search

Hybrid in the Real World

The landscape "I" see.  This part is where I get yelled at, or perhaps I'm seen as a Fanboy.  I'll just say what I saw.

In the year 2013 - I saw the future that Microsoft wanted was all Cloud.  I was very dismayed - Australia is not particularly fast at going to the cloud.  Many of our enterprises aren't even migrating their SharePoint installations from 2010.  What about data sovereignty?  In the light of NSA spying case in 2014 it looked even worse. 

In the following year June 2014, the Australian Government modified its policy to say it is up to each Department Head to decide whether it is OK to store data offshore.  No doubt pushed by both budget cuts, internal push, external Vendor Pricing and a public statement of cutting out unnecessary Red-Tape.

Now, I hear cloud being implemented left and right.  Prime examples?

  • Exchange Online - much bigger mailboxes than on-prem.  Mobile friendly.
  • To get Exchange Online, a company pretty much has their Active Directory synchronized to Azure AD.  ADFS is nicer for SSO, but more servers.  Small and medium enterprises are pretty happy with DirSync.  That's another tick.
  • OneDrive for Business - relatively large personal storage space that allows Enterprise IT control
  • Office Client Licenses.  As part of the Office 365 package, the cheaper client licenses (and up to 5 devices, as well as additional mobile/tablet licenses) are also a huge win.
  • Yammer - Corporate-friendly, sanctioned "social platform".  Seriously, your youngster employees wants to talk, at least give them the right place to make that conversation heard.
  • Lync/Skype for Business - Lync Online took care of a lot of remote VOIP scenarios.  Lync Server worked well with Polycom and other On-Prem solutions.
  • Extranet Sites (SharePoint) where the company wants to share "some" content with an external partner.

There will always be companies that can't move everything to the cloud, but I think more and more companies are considering what they *could* move.  Most companies don't really want to host their own Exchange Server, unless they really have to.  And even for those rare cases, my Bank client is implementing Yammer as their Enterprise Social solution.

 

Reading the Tea Leaves

Oh my favourite activity.  I love doing this and yet I'm so bad at it.

I'm terrible at reading the future.  So I only wanted to mostly comment on the past.  Perhaps as a consultant that works across many different sectors (building, education, transport, mining and banking), and as a community person that loves to talk to everybody I meet, I do see quite a bit.

And what I see aligns with what Microsoft is doing.  So I think it's safe to predict this one:

Bet on the Cloud.  And if you can't do that yet, Bet on Hybrid.

 

SharePoint Add-in: Accessing Webcam with Only Javascript

This blog post details how to access your Webcam via Javascript through the browser, and upload that content to a SharePoint library.  Then, with an added bonus, set it to be your User Profile Picture.

I have build this User Profile Webcam solution as an Add-in for SharePoint (was App for SharePoint).  This is a SharePoint Hosted app.  All the code runs in the browser and access SharePoint via the SharePoint Online API.

Step 1.  Access your webcam.

Modern Browsers:  Immersive Internet Explorer, Firefox, Chrome and Safari all has ways to access your webcam via

getUserMedia

 

But this doesn't work on IE (Desktop).  So for that we'll add a Polyfill (Flash). 

 

There's a project on github that does this nicely, and I use large chunks of the code from their demo.

https://github.com/addyosmani/getUserMedia.js

<script type="text/javascript" src="../Scripts/html5.js"></script>
<script type="text/javascript" src="../Scripts/getUserMedia.min.js"></script>
<!-- Add your JavaScript to the following file -->
<script type="text/javascript" src="../Scripts/App.js"></script>


<div id="webcam"></div>
<canvas id="canvas" height="240" width="320"></canvas>

<br />
<button class="btn" style="width:140px;" id="takeSnapshot">Take a picture</button>
this.snapshotBtn = document.getElementById('takeSnapshot');
$(this.snapshotBtn).click(this.getSnapshot);

this.getSnapshot = function () {
    // If the current context is WebRTC/getUserMedia (something
    // passed back from the shim to avoid doing further feature
    // detection), we handle getting video/images for our canvas 
    // from our HTML5 <video> element.
    if (App.options.context === 'webrtc') {
        var video = document.getElementsByTagName('video')[0];
        //App.canvas.width = video.videoWidth;
        //App.canvas.height = video.videoHeight;
        App.canvas.getContext('2d').drawImage(video, 0, 0, App.canvas.width, App.canvas.height);

        // Otherwise, if the context is Flash, we ask the shim to
        // directly call window.webcam, where our shim is located
        // and ask it to capture for us.
    } else if (App.options.context === 'flash') {
        window.webcam.capture();
        App.changeFilter();
    }
    else {
        alert('No context was supplied to getSnapshot()');
        return false;
    }
}

The code basically takes either the webrtc object and copy the image to the App canvas.  Or use the flash polyfill and Flash will write the image to the canvas element.

 

Step 2. Binary Format

This part is hairy.  But you are a developer, and dealing with Binary Encoding in JavaScript is what we do.

/*
Now, get canvas and do decoding.
1. canvas can return image data in png, in dataURL format.
2. strip the heading string "data:image/png;base64,"
*/

var ctx = App.canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, App.canvas.width, App.canvas.height);
var dataURL = App.canvas.toDataURL('image/png');
var base64 = dataURL.replace(/^data:image\/png;base64,/, "");

This is base64 dataUrl format.

 

/*
Reading.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#Solution_.232_.E2.80.93_rewriting_atob%28%29_and_btoa%28%29_using_TypedArrays_and_UTF-8

We need to convert DataURL to ByteArray
Mozilla has these two functions.
*/

function b64ToUint6(nChr) {
    return nChr > 64 && nChr < 91 ?
        nChr - 65
      : nChr > 96 && nChr < 123 ?
        nChr - 71
      : nChr > 47 && nChr < 58 ?
        nChr + 4
      : nChr === 43 ?
        62
      : nChr === 47 ?
        63
      :
        0;
}

function base64DecToArr(sBase64, nBlocksSize) {
    var
      sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
      nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen);

    for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
        nMod4 = nInIdx & 3;
        nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 6 * (3 - nMod4);
        if (nMod4 === 3 || nInLen - nInIdx === 1) {
            for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
                taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
            }
            nUint24 = 0;
        }
    }
    return taBytes;
}

var byteArray = base64DecToArr(base64);

This gives us ByteArray.  We're getting close.

I'd like to tell you that I spend a good 3 evenings working to this point.  Try debugging that code and see if your eyes bleed!

/*

Finally, 

The ByteArray needs to be encoded as a string, and in the POST call, "binaryStringRequestBody" needs to be "true"

https://msdn.microsoft.com/en-us/library/office/dn769086.aspx
http://blogs.msdn.com/b/uksharepoint/archive/2013/04/20/uploading-files-using-the-rest-api-and-client-side-techniques.aspx
http://sharepoint.stackexchange.com/questions/54218/sharepoint-2013-rest-api-upload-image

*/

var binaryString = '';
var len = byteArray.byteLength;
for (var i = 0; i < len; i++) {
    binaryString += String.fromCharCode(byteArray[i])
}

SharePoint API wants data in BinaryString format.  Which is basically each byte of the ByteArray encoded as a concatenated string.

 

 

Step 3.  Upload to SharePoint

/*
Use RequestExecutor to post file back to sharepoint
*/

var appWebUrl = context.get_url();
var requestExecutor = new SP.RequestExecutor(appWebUrl);

var uploadPictureEndPoint = appWebUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/Files/add(url=@TargetFileName,overwrite='true')?" +
    "&@TargetLibrary='" + "Pictures" + "'" +
    "&@TargetFileName='" + _spPageContextInfo.userLoginName + ".png" + "'";

/*
Using RequestExecutor, don't need REQUESTDIGEST
*/
//var digest = $("#__REQUESTDIGEST").val();

requestExecutor.executeAsync({
    url: uploadPictureEndPoint,
    method: "POST",
    headers: {
        "Accept": "application/json;odata=verbose"
    },
    contentType: "application/json;odata=verbose",
    binaryStringRequestBody: true,  // binaryStringRequestBody must be true
    body: binaryString,
    success: function (x, y, z) {
        alert("Success! Your file was uploaded to SharePoint.");
    },
    error: function (x, y, z) {
        alert("Oooooops... it looks like something went wrong uploading your file.");
    }
});

 

Step 4.  Setting User Profile Picture.

 

var appWebUrl = context.get_url();
var requestExecutor = new SP.RequestExecutor(appWebUrl);
var setPictureEndpoint = appWebUrl + "/_api/sp.userprofiles.peoplemanager/setmyprofilepicture";

requestExecutor.executeAsync({
    url: setPictureEndpoint,
    method: "POST",
    headers: {
        "Accept": "application/json;odata=verbose"
    },
    contentType: "application/json;odata=verbose",
    binaryStringRequestBody: true,
    body: App.binary,
    success: function (data) {
        alert('Set My Profile Picture succeeded, it will take a few seconds for the change to be propagated.');
    },
    error: function (error) {
        alert("Oooooops... it looks like something went wrong updating your profile picture (no permission?).");
    }
});

For this API call to /setmyprofilepicture to succeed, your App must have additional permissions.

This will ask the user (or Tenant Administrator) to grant the correct permission.

If you don't grant this permission, the webcam can still save picture to library, but it won't be able to set picture as user profile picture.

Summary

  • Get Webcam via Browser
  • Process canvas data to imageUrl to byteArray to BinaryString
  • Upload to SharePoint Library
  • Set as User Profile Picture
  • Achieve Zoolander face!
  • You can download the App for free from the Office Store to see it in action.  
    Feel free as a developer to hit F12 and step through the code.
  • Leave a comment below and let me know what you think.  For example, I think there's a need for an Outlook Add-in that uses the webcam.  Especially, if it can run on an iPad.