Save Your SharePoint Online Tenant: The SharePoint Sandboxed Solutions Inspector

If you’ve been following the “code-based sandbox solutions on Office 365” saga, you know that there is little time left to fix your existing sandbox solutions in Office 365. See: Microsoft Is Removing Code-Based Sandbox Solutions in SharePoint Online – Be Prepared!

Last week, Vesa Juvonen (@vesajuvonen) released a script (New Script Available from Microsoft PnP: Generate list of sandbox solutions from SharePoint Online tenant) that can help you find your sandbox solutions. Surprisingly, what was missing from Vesa’s script was identification of the solutions that contain code. You’d get a list of all your sandbox solutions, but not specifically the ones that were going to cause you problems.

The Rencore Team

Some of the great looking folks at Rencore. Where’s Waldek?

My awesome friends at Rencore – the SFCAF folks – were kind enough to make a free tool available this week to help with even more with your diagnosis and even some of the cures. In Erwin van Hunen’s (@erwinvanhunenpost Introducing the Rencore SharePoint Sandboxed Solutions Inspector, you can learn more about the free tool and how it can help.

On August 31st, 2016 Microsoft is going to shut down support for Sandboxed Solutions with code.

Sandboxed Solutions containing code will be deactivated and this might impact your Office 365 tenant big time!

If you know you need it, just head right on over to the download page.

The SharePoint Sandboxed Solutions Inspector

They have already released several updates to the tool, and are keeping it current based on feedback from the folks using it. Now that’s service – and for a free tool!

But I think the best thing is that the Rencore tool can fix some of the most common issues – most notably the “empty DLL” issue that makes Office 365 think you have code in your sandbox solution when you don’t.

We heard “30 days” when all this started, and now people seem to be taking that as August 31. Don’t leave your users in the lurch – get going on handling this situation.

Oh, and if you’re a vendor or consultant who has written a sandbox solution with code over the last few years: reach out to your client and own it. Get them back on the right road and you’ll be the better for it.

Clean report!

Clean report!

New Script Available from Microsoft PnP: Generate list of sandbox solutions from SharePoint Online tenant

As I wrote on Monday, the decision to begin removing already deprecated code-based sandbox solutions on SharePoint Online took many people by surprise. Even though the news about the deprecation has been out there since 2014, the abrupt move – especially during a time when many people are on summer vacation – caused consternation for some. Others welcomed the move, in essence saying “good riddance” to a model that never really reached maturity.

But what if you manage a tenant on Office 365 that might have code-based sandbox solutions? Maybe you’ve used outside vendors to build solutions for you and you’re not sure what techniques they have used. Or maybe your own team built some things a few years back, you’ve had some turnover, and you source control isn’t so great. (Not so unusual, frankly.) How do you know what you have and what to do about it? You certainly don’t want functionality your users actually need to stop working unexpectedly. Some of these solutions could be InfoPath forms with code-behind, for example.

Office 365 Dev Patterns & Practices (PnP) Vesa Juvonen (@vesajuvonen) – one of my true Microsoft heroes for what he has done with the PnP set of tools – has come to the rescue, apparently with some help from Karine Bosch (@kboske). They have released a PowerShell script today that promises to “Generate [sic] list of sandbox solutions from SharePoint Online tenant“. (Far be it from me to correct Vesa’s Finglish!)

Generate list of sandbox solutions from SharePoint Online tenant

Generate list of sandbox solutions from SharePoint Online tenant

This script can be used to generate list of sandbox solutions in SharePoint Online tenant. You will need to use tenant administrator account to connect to SharePoint Online and script will generate a list of sandbox solutions to separate txt file, which can be imported to Excel for further analyses.

Note: This script is relatively simple and does not use multi-threading, so execution in larger tenants might take a while. We are looking for further enhancing the script with multi-threading support, if there’s demand for this. Also community contributions on this side are more than welcome.

Output file has following columns

  • URL of the site collection
  • Name of the sandbox solution
  • Author field from the sandbox solution – who uploaded the file
  • Created field from the sandbox solution – when solution was uploaded
  • Status field – 1=Activated, 0=Not activated

What seems to be missing here – at least to me – is the “and this one contains ‘code'” indication, but it’s still going to be very useful. Maybe it’s not simple to tell which solutions contain code? If you have ideas about this, it’s an open source project, so head over there and enhance it!

IMPORTANT: Please note that this script lists ALL sandbox solutions. But only code-based sandbox solutions have been deprecated and are being removed from Office 365. So don’t panic when you see all of your no-code solutions and site templates listed. This is a first step in inventorying your solutions.

The script requires – not surprisingly – that you have the SharePoint Online cmdlets installed. My bet is that there are plenty of Office 365 customers that have never really figured out how to download, install, and use PowerShell against Office 365. Many tenants are run by business users rather than technical types, as befits a powerful SaaS offering. In fact, in many cases, IT doesn’t need to be involved at all. That said, one would hope that those tenant administrators would know whether they have sandbox solution installed. However, see my mention of possible situations above, even if IT was in charge.

Here’s a quick tutorial on how to install those cmdlets in case you need it. In writing this section, I’m stealing the TechNet article Connect to Office 365 PowerShell. (I fear this article may not be available to everyone, as TechNet is part of subscriptions? I’m not really sure.) To do this, you have to be a tenant administrator. If you are, you’re probably the one wondering what you have in any case. It’s a pretty painless process, but if you haven’t used PowerShell – think batch files for servers – then it might be intimidating. I’m just copying the instructions from TechNet here, but I’ll add in some graphics and additional comments over the course of the day, so come back if you have questions. If you want to add any tips, please do so in the comments.

As Vesa always says: Sharing is Caring!

Step 1: Install required software

These steps are required once on your computer, not every time you connect. However, you’ll likely need to install newer versions of the software periodically.

  1. Install the 64-bit version of the Microsoft Online Services Sign-in Assistant: Microsoft Online Services Sign-in Assistant for IT Professionals RTW.

Microsoft Online Services Sign-In Assistant for IT Professionals RTW

2. Install the 64-bit version of the Windows Azure Active Directory Module for Windows PowerShell: Windows Azure Active Directory Module for Windows PowerShell (64-bit version).

Windows Azure Active Directory Module for Windows PowerShell (64-bit version)

Step 2: Open the Windows Azure Active Directory Module

  1. Find and open the Windows Azure Active Directory Module for Windows PowerShell by using one of the following methods based on your version of Windows:
    • Start menu   On the Start menu, enter Azure in the Search programs and files box.
    • No Start menu   Search for Azure using any of these methods:
      • On the Start screen, click an empty area, and type Azure.
      • On the desktop or the Start screen, press the Windows key+Q. In the Search charm, type Azure.
      • On the desktop or the Start screen, move your cursor to the upper-right corner, or swipe left from the right edge of the screen to show the charms. Select the Search charm, and enter Azure.
  2. In the results, select Windows Azure Active Directory Module for Windows PowerShell.
Here's what it looks like on my laptop running Windows 10

Here’s what it looks like on my laptop running Windows 10

Step 3: Connect to your Office 365 subscription

  1. In the Windows Azure Active Directory Module for Windows PowerShell, run the following command.
    $UserCredential = Get-Credential
    
    Windows Azure Active Directory Module for Windows PowerShell running...
    Windows PowerShell Credential Request 
    

    In the Windows PowerShell Credential Request dialog box, type your Office 365 work or school account user name and password, and then click OK.

  2. Run the following command.
    Connect-MsolService -Credential $UserCredential
    Connect-MsolService -Credential $UserCredential - Success!

    Connect-MsolService -Credential $UserCredential – Success!

How do you know this worked?

After Step 3, if you don’t receive any errors, you connected successfully. A quick test is to run an Office 365 cmdlet—for example, Get-MsolUser—and see the results.

If the Get-MsolUser cmdlet runs successfully, you'll see a list of your users

If the Get-MsolUser cmdlet runs successfully, you’ll see a list of your users

If you receive errors, check the following requirements:

  • A common problem is an incorrect password. Run Step 3 again. and pay close attention to the user name and password you enter.
  • The Windows Azure Active Directory Module for Windows PowerShell requires that the Microsoft .NET Framework 3.5.x feature is enabled on your computer. It’s likely that your computer has a newer version installed (for example, 4 or 4.5.x), but backwards compatibility with older versions of the .NET Framework can be enabled or disabled. For more information, see the following topics:
  • Your version of the Windows Azure Active Directory Module for Windows PowerShell might be out of date. To check, run the following command in Office 365 PowerShell or the Windows Azure Active Directory Module for Windows PowerShell:
    (Get-Item C:\Windows\System32\WindowsPowerShell\v1.0\Modules\MSOnline\Microsoft.Online.Administration.Automation.PSModule.dll).VersionInfo.FileVersion
    

    If the version number returned is lower than the value 1.0.8070.2, uninstall the Windows Azure Active Directory Module for Windows PowerShell, and install the latest version from the link in Step 1.

  • If you receive a connection error, see this topic: “Connect-MsolService: Exception of type was thrown” error.

Using the Signature Pad jQuery Plugin with SharePoint & InfoPath – Redux

I’m using Thomas Bradley’s Signature Pad plugin for a project, which I’ve used successfully before. The twist this time is that I want to save the signature as an image rather than just as JSON.

There’s a method called getSignatureImage()  that works just fine to grab the signature as a base64 string, like so (this is the result for an empty canvas):

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAArwAAACWCAYAAAA8Els6AAAIjElEQ…QIECBg8LoBAgQIECBAgACBtIDBm65XOAIECBAgQIAAgQtmc1xdE+aNPAAAAABJRU5ErkJggg==

(Note the “…”; it’s a much longer string.)

Snazzy signatureI was having a problem saving the signature to a library successfully. Uploading the file was easy, but the image file was always ending up corrupted. It didn’t matter if I uploaded to a Document library or Picture Library; no joy.

I knew I was missing something obvious. I tried removing the leading “data:image/png;base64,”. I tried different values for Content-Type, etc. It had to be something about the way I was creating the file.

In the end, I got some great advice from a colleague. The base64 content has to be *decoded* so that we can save it. This is what worked:

// Get the base64-encoded image from the plugin
var img = signatureArea.getSignatureImage();
var outfile = fakefilename(); // I'm creating a unique filename to use for saving in my testing

$.ajax({
    url: _spPageContextInfo.webAbsoluteUrl +
        "/_api/web/getfolderbyserverrelativeurl('/path/to/my/picture/library/')/files/add(overwrite=true, url='" + outfile + "')",
    type: "POST",
    data: convertDataURIToBinary(img),
    processData: false,
    headers: {
        "accept": "application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val()
    },
    success: function() {
        $("#sig-file").attr("src", outfile); // Show the image file in the form
    }
});

// Useful function "borrowed" from http://sharepoint.stackexchange.com/questions/60417/cant-upload-a-non-text-file-to-sharepoint-app-via-rest-api
// Decodes the base64 text data back into the binary data representation of the image file
var convertDataURIToBinary = function(dataURI) {
    var base64Marker = ";base64,";
    var base64Index = dataURI.indexOf(base64Marker) + base64Marker.length;
    var base64 = dataURI.substring(base64Index);
    var raw = window.atob(base64);
    var rawLength = raw.length;
    var array = new window.Uint8Array(new window.ArrayBuffer(rawLength));

    for (i = 0; i < rawLength; i++) {
        array[i] = raw.charCodeAt(i);
    }
    return array;
};

SharePoint Forms and Workflow – A Different Perspective

advanced-formWhenever I get into conversations about forms in SharePoint (or anywhere else for that matter), the conversation almost always turns immediately for workflow. It seems to greatly surprise a lot of people when I say that sometimes workflow is irrelevant for forms. I’d say that 80%+ of SharePoint forms have no workflow at all. (I think it’s a higher percentage, but I know many of you live and die by workflow.)

I think forms and workflow are too often intertwined as concepts, making the forms discussion overly complex.

IMO, forms are for collecting or editing data. Workflows are for managing that data. By keeping those two concepts discrete, we can have excellent forms that just do what forms should do.

Conflating the two will probably delay the possibility of a robust new form tool for SharePoint. We know that something is coming to replace InfoPath, but we don’t know what it is yet.

We learned early in 2014 that InfoPath is dead. In actuality, it’s not dead; it’s only entered its twilight years. We have until 2023 before it isn’t “supported” anymore, and it will probably be useful for many people even after that. (I won’t make any snarky comments about “supported” software.)

I made up the 80% number above based on my own experience. It really depends on type of SharePoint installation you’re working in. My work is more toward the KM and Intranet side of things, and it’s very rare that I end up implementing a workflow. Knowledge workers can be trusted to do their work in the right way to create value, and it’s rarely a sequential or predictable thing. The few cases where workflows matter – time sheet submission, time off requests, article posting, etc. – the workflows tend to be very simple.

For similar reasons, I haven’t seen much need for InfoPath. With a little JavaScript and CSS, I can usually layer a veneer over the default list forms to give them any boost they need to meet business needs. Even so, the default list forms are fine probably 90%+ (another number I’m making up) of the time.

So much of this depends on the culture of the organization, too. If it’s an open and trusting culture, workflows come up infrequently. If it’s more of a command and control culture, they want workflows for everything. That is until you ask them to describe the repeatable process and they realize that there really isn’t one. Either they have to define a real process (lots of hard work) or they keep doing things the way they have – in a slightly disorganized way that still works.

My point is that assuming that there’s always a coupling of forms and workflow means that everything gets more complicated fast. I like the fact that forms and workflow are separate but connectable in SharePoint now. It means I can plug in a workflow if and when I need it; the forms engine isn’t too cluttered by the workflow artifacts.

What is your experience on this? Are forms and workflow always intertwined or are they really two separate ideas? I’ve created a little poll below to capture your feelings on this. Add your voice into the mix and I will try not to use the statistics inappropriately, as do may others.

Using the Signature Pad jQuery Plugin with SharePoint & InfoPath

Have you ever needed to capture signatures in SharePoint forms? What about InfoPath forms? I’m betting many people have seen a need to do this but have balked at the cost or complexity of the available solutions.

A client of mine named Cody Sellers (@codyjsellers), who works for Mercom Corporation had just such a need. Mercom is a smallish company that didn’t want to spend tens of thousands of dollars on a “real” eSig solution. Cody did some research and found a great jQuery plugin and turned to me for help implementing it. In this case we’re working with SharePoint 2013 on premises, but this should work with any version of SharePoint with some modification.

2014-06-18_13-38-20

The Signature Pad plugin comes from a clearly smart guy named Thomas J Bradley. Based on the work I’ve done with it so far, it’s well written with good documentation. Even better: it just plain works.

Signature Pad: A jQuery plugin for assisting in the creation of an HTML5 canvas based signature pad. Records the drawn signature in JSON for later regeneration.

The plugin allows you to capture signatures based on typed or drawn input and can be saved as JSON or a PNG image.

In our case, we wanted to be able to add a signature block at the bottom of multiple InfoPath forms. To make this work well, we decided that we would add the script into the master page. Yes, that may seem foolhardy, but a large majority of the work that is going to be happening in this Site Collection is filling out these forms.

Because of this, we needed a clear convention to follow in every form so that we could locate all of the signature fields (some forms have more than one) and add the signature pad capability reliably.

One of the hardest things about working with InfoPath forms client-side is that there are almost no sensible hooks in the emitted DOM. Everything is a .NET-like element id, like

ctl00_ctl33_g_3782ce51_9259_4854_80a0_e6355e54b690_FormControl0_V1_I1_T6

Since those ids can change anytime you change the form, you really want to give yourself something more solid to hook into. (At least we know that InfoPath won’t change anymore and the basic form elements are constant!)

To ensure that we could easily add the signature pad to any existing signature fields, we wrapped each signature field in an InfoPath section with its ScreenTip set to “==Signature==”.

InfoPath section with its ScreenTip set to ==Signature==

InfoPath section with its ScreenTip set to ==Signature==

The logic works like this:

  • Check to see if there’s a div with its id ending in ‘XmlFormView’ – this is the container for an InfoPath form. If there isn’t one, do nothing.
  • Find sections in the form that have their ScreenTip set to “==Signature==”. Sections have a fieldset container, and the title is set to the ScreenTip value. In other words, any section with its ScreenTip set to “==Signature==” will be treated as a signature area by the code.
  • Find the input element inside that section. This is the field where we want to store the JSON representation of the signature.
  • If the input element has no value, render the signature pad for signing (new forms)
  • If the input element already has a value, show the signature it contains (edit and display forms)

We decided to store the JSON representation of the signature in a field in each InfoPath form. Since the JSON represents the vectors from an HTML5 canvas, it’s a nice, standard way to store it. Images would require some separate repository or further encoding to be stored in a text field. Since the plugin can both emit and reconstitute the JSON into a signature, it’s a good way to go.

We ended up with the code below for the simplest case. (It’ll get more complicated as we get into the business rules, but this post is about the technology to capture and display the signatures.) When we create a new item in the newifs.aspx form, the Signature column will be empty and thus we will show the signature pad. If there’s already a value in the field, then we’re on the edit or display form, and signing has already happened so we just display the signature we have.

I’ve added comments that hopefully make the code simple enough to follow. As with all code you read on the Web, this isn’t just a drop-it-in-and-it’ll-work thing – you’ll end up tailoring it for sure. (One would think I wouldn’t need these caveats, but…)

$(document).ready(function() {
    // Find the guts of the InfoPath form in the page
    var infoPathContainer = $("div[id$='XmlFormView']");
    // if there's no InfoPath form, then we have nothing to do here (escape early)
    if (infoPathContainer.length !== 0) {
        setupSignatures(infoPathContainer);
    }
});
// There's an InfoPath form in the page, so look for signature fields and set them up
function setupSignatures(infoPathContainer) {
    // The Signature "signature" in the form. This makes it easy to select the proper form elements
    var signatureSignature = "==Signature==";
    // The signature field should be inside a section with its tooltip=signatureSignature
    var signatureContainer = infoPathContainer.find("fieldset[title='" + signatureSignature + "']");
    // The Signature field is the lone input element in the section
    var signatureBox = signatureContainer.find("input");
    // We may have multiple signature fields in the form
    signatureBox.each(function() {
        var thisSignatureBox = $(this);
        // We need a reference to the fieldset which represents the section several times
        var thisSignatureFieldset = thisSignatureBox.closest("fieldset");
        // Clean up the display by hiding the field we're using to store the JSON
        thisSignatureBox.hide();
        // The signature data is the value of the signatureBox
        var signatureData = thisSignatureBox.val();
        // If there's no signature yet...
        if (signatureData === "") {
            // Add the appropriate markup to the page
            thisSignatureBox.before(buildSignatureBox());
            // Bind to the click event for the 'Ready to Sign' button
            thisSignatureFieldset.find(".signature-ready").click(function() {
                $(this).toggle();
                $(this).next("div").toggle();
            });
            // Activate the signature pad in drawOnly mode
            var signatureArea = thisSignatureFieldset.find(".signature-box").signaturePad({
                drawOnly: true,
                lineTop: 125,
                output: thisSignatureBox,
                onBeforeValidate: function(context, settings) {
                    thisSignatureBox.focus(); // Needed to fire the change events bound to the field
                    thisSignatureBox.blur(); // Needed to fire the change events bound to the field
                    thisSignatureBox.hide(); // In case it becomes visible again
                }
            });
            // When the user clicks the button below the signature pad, validate
            thisSignatureFieldset.find(".signature-done").click(function() {
                thisSignatureFieldset.find("p.error").remove();
                signatureArea.validateForm();
            });
            // If we already have signature data, just show the existing signature
        } else {
            // Add the appropriate markup to the page
            thisSignatureBox.before(buildSignatureDisplay());
            // Activate the signature pad in displayOnly mode
            thisSignatureFieldset.find(".sigPad").signaturePad({
                displayOnly: true
            }).regenerate(signatureData);
        }
    });
}
// Function to emit the markup for the signature pad in signing mode
function buildSignatureBox() {
    var signatureBox = "<div class='signature-box'>" +
        "<input class='signature-ready' type='button' value='Ready to sign'/>" +
        "<div style='display:none;'>" +
        "<ul class='sigNav'>" +
        "<li class='drawIt'><a href='#draw-it'>Sign Here</a></li>" +
        "<li class='clearButton'><a href='#clear'>Clear</a></li>" +
        "</ul>" +
        "<div class='sig sigWrapper'>" +
        "<canvas class='pad' width='700' height='150'></canvas>" +
        "<input type='hidden' name='output' class='output'>" +
        "</div>" +
        "<input class='signature-done' type='button' value='Capture signature'/>" +
        "</div>" +
        "</div>";
    return signatureBox;
}
// Function to emit the markup for the signature pad in display mode
function buildSignatureDisplay() {
    var signatureDisplay = "<div class='sigPad signed'>" +
        "<div class='sigWrapper'>" +
        "<canvas width='700' height='150' class='pad'></canvas>" +
        "</div>" +
        "</div>";
    return signatureDisplay;
}

Here are some screenshots from our proof of concept:

The form when it loads

The form when it loads

Ready to sign

Ready to sign

Signed

Signed

Signature displayed on the display form

Signature displayed on the display form

Snippet of the data that is stored for a signature

Snippet of the data that is stored for a signature

 

Interested in saving the signature as an image file? Check out my follow up post: Using the Signature Pad jQuery Plugin with SharePoint & InfoPath – Redux