Archives

Archives / 2012 / September
  • SharePoint 2013: Building your own WOPI Client, part 3

    Tags: SharePoint 2013, WOPI, WAC Server

    This is part three (and counting) of my Building your own WOPI Client series. In part 1 I discussed the WOPI Protocol and especially how to implement the Discovery process of a WOPI Client. In part 2 we built a viewer application as a WOPI Client and connected it to SharePoint. In this part we’re modifying our WOPI Client to support basic editing of the files.

    Modyfing the WOPI Client Discovery data

    The first thing that we need to do is to modify our Discovery method, in our case the static XML file, to tell the WOPI Server that we support editing of the files. It’s a simple procedure and we just add another line of XML like this to the app element:

    <action name="edit"
        ext="cs"
        default="false"
        urlsrc="http://wacky.corp.local/csharp/editor.aspx?&lt;theme=THEME_ID&amp;&gt;" />
    

    This action has the name set to edit, to tell the WOPI Server that we now support editing of cs files. The default attribute is set to false (the view mode is the default one) and we set the urlsrc to point to our editor.aspx file (that we’ll build in a minute).

    Once this is done, the WOPI Server will not pick that change up automatically. Instead we need to manually refresh the WOPI Bindings in SharePoint by removing them and then add them back again:

    Remove-SPWOPIBinding -Server wacky.corp.local
    Add-SPWOPIBinding -Server wacky.corp.local -AllowHTTP

    An IISRESET might be necessary for SharePoint to pick this up.

    Modifying the Viewer

    To be able to switch to Edit mode from the viewer we slightly update the user interface to include an Edit button so that it looks like below. As you can see I also added a Close button, which just return the user to the Document Library where the file resides.

    image

    We’re not covering the HTML and CSS for this, but this is two asp:HyperLink controls. In the Page_Load method, when retrieving the metadata for the document, the NavigateUrl for these links are set like this:

    Close.NavigateUrl = d["CloseUrl"];
    Edit.NavigateUrl = d["HostEditUrl"];
    Edit.Enabled = !bool.Parse(d["WebEditingDisabled"]);

    First I retrieve the CloseUrl from the document metadata and pass into the Close Hyperlink control, next I take the HostEditUrl and configure the Edit Hyperlink control. The URL in the HostEditUrl will contain the URL/urlsrc that is specified as the edit action in the discovery document. Finally I enable the Edit link if the WebEditingDisabled is set to false.

    imageIf we now take a look at the UI in SharePoint we have enabled a couple of ways to edit the document; from the viewer and also from the Document fly-out in the document libraries. Just as the WAC Server does with Office documents.

    Building the Editor

    Creating the editor is very much the same as the viewer. I created a Web Form called editor.aspx in the web project and copied the HTML from the viewer to the new editor.

    I modified the menu in the editor.aspx so that it has View and Save buttons instead of the Edit button. The View button takes the user to the WOPI Client viewer and the Save button saves the document back to the WOPI Server (SharePoint in this case). The Save button is an asp:LinkButton so that I can have a server side callback for that method.

    The Page_Load method in the editor should look like below. It is very similar to the viewer with a few exceptions:

    string _src;
    protected void Page_Load(object sender, EventArgs e)
    {
        _src = Request.QueryString["WOPISrc"];
    
        if (!String.IsNullOrEmpty(_src))
        {
            access_token.Value = Request.Form["access_token"];
            access_token_ttl.Value = Request.Form["access_token_ttl"];
    
            if (!this.IsPostBack)
            {
                // Get the metadata
                string url = String.Format("{0}", _src);
                using (WOPIWebClient client = new WOPIWebClient(url, access_token.Value, access_token_ttl.Value))
                {
                    string data = client.DownloadString(url);
                    JavaScriptSerializer jss = new JavaScriptSerializer();
                    var d = jss.Deserialize<Dictionary<string, string>>(data);
                    hlSite.NavigateUrl = d["BreadcrumbFolderUrl"];
                    hlSite.Text = d["BreadcrumbFolderName"];
                    lblDocument.Text = d["BaseFileName"];
                    Close.NavigateUrl = d["CloseUrl"];
                    View.NavigateUrl = d["HostViewUrl"];
                    Save.Enabled = (bool.Parse(d["SupportsUpdate"]) && !bool.Parse(d["ReadOnly"]));
                }
    
                // Get the content
                url = String.Format("{0}/contents", _src);
                using (WOPIWebClient client = new WOPIWebClient(url, access_token.Value, access_token_ttl.Value))
                {
                    string data = client.DownloadString(url);
                    textarea.InnerText = data;
                }
            }
        }
    }

    I’ve made a couple of changes compared to the viewer. First of all the access_token and access_token_ttl are now hidden input elements on the page instead of local strings, since we need to preserve those values between postbacks because they are needed on all requests back to the WOPI Server. When reading the metadata for the document I set the View button to get the HostViewUrl which is the WOPI Client view URL. Then finally I only enable the Save button if the document supports updates and is not read only. All this is only done if it’s not a postback.

    Instead of using the nice syntax highlighter for presenting the document we’re instead using a textarea control, in which we add the contents of the document.

    Saving the document

    To save the document we need an event handler on the Save button, like this:

    protected void Save_Click(object sender, EventArgs e)
    {            
        string url = String.Format("{0}/contents", _src);
        using (WOPIWebClient client = new WOPIWebClient(url, access_token.Value, access_token_ttl.Value))
        {
            client.Headers.Add("X-WOPI-Override", "PUT");
            try
            {
                client.UploadString(url, textarea.InnerText);
            }
            catch (WebException we) {
                HttpWebResponse response = we.Response as HttpWebResponse;
                ErrorPanel.Visible = true;
                switch (response.StatusCode)
                {
                    case HttpStatusCode.NotFound:
                        ErrorPanel.Controls.Add(new LiteralControl("File unknown/user unauthorized"));
                        break;
                    case HttpStatusCode.Unauthorized:
                        ErrorPanel.Controls.Add(new LiteralControl("Invalid token"));
                        break;
                    case HttpStatusCode.Conflict:
                        ErrorPanel.Controls.Add(new LiteralControl("Lock mismatch"));
                        break;
                    default:
                        ErrorPanel.Controls.Add(new LiteralControl("Unknown error"));
                        break;
                                
                }
            }                
        }
    }
    The code is pretty straight forward and most of it is exception handling. First of all the communication back to the WOPI Server are done on the same URL as when retrieving the document contents, with the difference that we’re using the HTTP POST method (UploadString method). To be able to update a document you must set the X-WOPI-Override header to “PUT”. If you don’t do this you will get a 404 error.

    As you can see I added error handling here as well. To the editor page I added an asp:Panel control which can be used to show any errors. For instance we need to handle cases when someone checked-out the file and set it to read only mode etc. For full information on all possible errors see the [MS-WOP] 3.3.5.3.2 specification.

    That was it, all coding should be done by now. Of course you could pimp the user interface with some nice “Saving..” animated gifs etc.

    Taking it for a test drive…

    If everything compiles fine it’s time to test the editor. Upload a .cs file to any document library and click on it to get to the viewer. From the viewer you should then be able to click on the Edit button and get to the editor. In the editor modify your file and click Save. Verify that your changes is preserved by clicking on the View button or Close the editor and go back to the viewer from the document library. The editor should look something like this:

    image

    Summary

    Now you’ve seen how easy we can build the editor, using the helper classes that we produced in part 2. From here on you should be able to build your own viewers and editors. Make sure that you read the [MS-WOPI] protocol specification so that you now how to handle all the different situations with locked files etc.

  • SharePoint 2013: Building your own WOPI Client, part 2

    Tags: SharePoint 2013, WOPI, WAC Server

    Welcome back to another part in my Building a WOPI Client series. In the previous and first post I walked you through the basics of the WOPI protocol, how the WOPI Discovery mechanism worked and how to implement it and finally how to register a WOPI Client with SharePoint 2013 as WOPI Server. In this post we’ll continue to build on our C# Viewer and now actually add the viewer – we ended the last post quite dull with just showing a simple Hello WOPI web page which we now are going to turn into a real C# viewer.

    The WOPI Protocol Specification (currently) lacks some details…

    The WOPI (Web Application Open Platform Interface) Protocol specification is currently in draft mode (version 0.2) and most of what is in this post is not defined in the specs, especially regarding the security and signing stuff. All the details in this post comes from extensive trial and error, and might be wrong – I’m trying to keep the post up to date once the specs are finalized.

    How the WOPI Client and WOPI Server interaction works

    Before we take a look at the code and how it’s implemented it is crucial to understand how the WOPI Client and WOPI Server talks to each other. When a user request to use one of the WOPI app actions the server will send a request to the WOPI Client on the address specified in the Discovery document (the urlsrc attribute specifies the address). The WOPI Server sends the “callback” address and access tokens to the WOPI Client so that the client can initiate contact and request the required resources to generate the WOPI Client interface. The callback URL is sent in the query string to the client and has the name WOPISrc,. It also sends an access token and its time to live as the request body. The WOPISrc URL has the following form HTTP://server/<...>/wopi*/files/<id> as described in the [MS-WOPI] 3.3.5. This URL is used by the client to request information about the document that it is going to generate the view for. For instance if we do an HTTP GET to the WOPISrc URL then we get metadata about the document, if we do an HTTP GET to the WOPISrc and appends “/contents” we get the actual content of the document or an HTTP POST to the “/contents” means that we’re updating the document.

    WOPI Security

    We cannot just request the resources as mentioned above, the WOPI client must also pass the access token, given to the client from the server, to make sure that the WOPI Client just don’t try to access files it doesn’t have access to. The access token must be sent as a query string parameter to the WOPI Server and as the Authorization header of the HTTP request. When building a WOPI Client we don’t have to care about what’s in the actual access token, just that it’s there and that we need to pass it on every request. But this is not enough we also need to send the X-WOPI-Proof header back to the WOPI Server. Remember from the last post that we specified a proof-key (which is the public version of the key), it is here that private key is used, to sign the X-WOPI-Proof header. Most of this is not yet in the [MS-WOPI] specs, so bare with me if I got something wrong. But hey, it works on my machine!

    Building a WOPIWebClient

    Since we need to add quite a few headers and query string parameters to each and every request from our WOPI client to the WOPI Server I have baked all this into a WebClient derivative, called WOPIWebClient. This class will make sure that all the necessary plumbing are there for us to make secure calls to the server:

    public class WOPIWebClient: WebClient
    {
        private readonly string _access_token;
        private readonly string _access_token_ttl;
        private readonly DateTime _utc; 
    
        public WOPIWebClient(string url, string access_token, string access_token_ttl)
        {
            _access_token = access_token;
            _access_token_ttl = access_token_ttl;
            _utc = DateTime.UtcNow;
        }
        protected override WebRequest GetWebRequest(Uri address)
        {
            UriBuilder builder = new UriBuilder(address);
    
            string append = String.Format("access_token={0}&access_token_ttl={1}", 
                _access_token, 
                _access_token_ttl);
    
            if(builder.Query == null || builder.Query.Length <=1){
                builder.Query = append;
            } else {
                builder.Query = builder.Query.Substring(1) + "&" + append;
            }
    
            WebRequest request = base.GetWebRequest(builder.Uri);
            if (request is HttpWebRequest)
            {
                // Add AuthZ header
                request.Headers.Add(
                    HttpRequestHeader.Authorization, 
                    String.Format("Bearer {0}", 
                        HttpUtility.UrlDecode(_access_token.Replace("\n", "").Replace("\r", ""))));
    
                request.Headers.Add(
                    "X-WOPI-Proof", 
                    WOPIUtilities.Sign(
                        WOPIUtilities.CreateProofData(builder.Uri.ToString(), _utc, _access_token)));
                    
                // TODO: Add X-WOPI-ProofOld here
    
                request.Headers.Add(
                    "X-WOPI-TimeStamp", 
                    _utc.Ticks.ToString(CultureInfo.InvariantCulture));
    
                request.Headers.Add(
                    "X-WOPI-ClientVersion",
                    "Wictor.WOPIClient.1.0");
    
                request.Headers.Add(
                    "X-WOPI-MachineName",
                    Environment.MachineName);
            }
            return request;
        }
    }

    Let’s walk through the code. First of all we have a constructor that takes three arguments; the url to use to call back to the WOPI Server and the access_token and the time to live value for the access token (access_token_ttl). Then I override the GetWebRequest method of the WebClient so that I can add the required headers and modify the URL before it sends it away. First of all in that overridden method we need to add the access token and the ttl to the query string (as access_token and access_token_ttl). Once that is done we can create the WebRequest and then add our headers. First of all is to add the Authorization header, that header must have the value of “Bearer <access_token>” (see [MS-WOPI] 2.2.1).

    It’s now where things starts to get tricky. We need to send the X-WOPI-Proof header to the server. This is a set of not so random bytes that are signed using our private proof key. In the code above I first create the proof using a utility method called CreateProofData and then sign the data using another utility method, called Sign. We’ll take a look at the implementation of those in a minute. If you are changing proof keys you should also add the old proof by using the X-WOPI-ProofOld header. After that I add the current time stamp in the X-WOPI-TimeStamp header.

    Finally I add a couple of other headers (also defined in 2.2.1) which are optional but might help you debug or troubleshoot your WOPI Client. When SharePoint is the WOPI Server you can see this data for all incoming requests in the ULS logs.

    Creating the proof data

    The proof data must be correctly generated otherwise the WOPI Server will not accept the request. The proof data is an array of bytes consisting of the access token, the requested url and the current timestamp, which we also send in clear text through the HTTP headers. Since the X-WOPI-Proof header is also signed before it is sent to the server, the WOPI Server can validate that no one tampered with the url or token. This is how the proof data is created:

    public static byte[] CreateProofData(string url, DateTime time, string accesstoken)
    {
        UTF8Encoding encoding = new UTF8Encoding();
        byte[] accessbytes = encoding.GetBytes(
            HttpUtility.UrlDecode(accesstoken));
        byte[] urlbytes = encoding.GetBytes(
            new Uri(url).AbsoluteUri.ToUpperInvariant());
        byte[] ticksbytes = getNetworkOrderBytes(time.Ticks);
    
        List<byte> list = new List<byte>();
        list.AddRange(getNetworkOrderBytes(accessbytes.Length));
        list.AddRange(accessbytes);
        list.AddRange(getNetworkOrderBytes(urlbytes.Length));
        list.AddRange(urlbytes);
        list.AddRange(getNetworkOrderBytes(ticksbytes.Length));
        list.AddRange(ticksbytes);
        return list.ToArray();
    }

    Using the access token, URL and time/ticks we convert them to byte arrays and add them to a list of bytes, each byte array is prefixed with the length of the byte array. Finally the list is casted into one big byte array – this is our proof that will be signed. Also worth to notice is that the Ticks and length bytes has to be converted to the reversed order, that is flip the byte order significance. For that I use a method, with two overloads, like this:

    private static byte[] getNetworkOrderBytes(int i)
    {
        return BitConverter.GetBytes(IPAddress.HostToNetworkOrder(i));
    }
    private static byte[] getNetworkOrderBytes(long i)
    {
        return BitConverter.GetBytes(IPAddress.HostToNetworkOrder(i));
    }

    Signing the proof data

    To make sure that the data is not tampered with or created by some fake WOPI client, we need to sign this proof data. This is done using the private key we generated in the first post. We used the following script to generate the proof-key (used by the WOPI Server to verify the signed data) and the data with a private key that is used to sign the proof data:

    $crypt = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 2048
    $proof = [System.Convert]::ToBase64String($crypt.ExportCspBlob($false))
    $proofwithkey = [System.Convert]::ToBase64String($crypt.ExportCspBlob($true))
    $proof
    $proofwithkey
    

    In our signing method we’ll use the $proofwithkey value, like this (the actual data is cropped for obvious reasons):

    public static string Sign(byte[] data)
    {
        using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider(2048))
        {
            provider.ImportCspBlob(
                Convert.FromBase64String("BwIAAACkA...yMp3k="));
            var signed = provider.SignData(data, "SHA256");
            return Convert.ToBase64String(signed);
        }
    }

    This method takes the byte array (proof data) as input, encrypts it using our private proof key and then returns the data as a Base64 encoded string. This is demo code and the private proof key is in this case hardcoded – in a production system you would like to have this stored in a secure and manageable store.

    The VS2012 SolutionThat is all that is required to make a WOPI WebClient, now let’s put it into use…

    Building the UI

    To build the C# Viewer I decided to use Syntax Highlighter by Alex Gorbatchev, so I downloaded the latest drop and added the script and CSS files to the Web project for the WOPI Client. I also added a custom CSS that I use for some custom UI styling (remember that I’m a kick ass UI designer as well!).

    Next up is to create our user interface. The viewer.aspx file that we created in the previous post with just some dummy text is next up on the agenda.

    First of all is to add the scripts and CSS files into the head element, like this:

    <head runat="server">
        <title></title>
        <link href="/styles/WOPIClient.css" rel="stylesheet" />
        <script type="text/javascript" src="/scripts/shCore.js"></script>
        <script type="text/javascript" src="/scripts/shBrushCSharp.js"></script>
        <link type="text/css" rel="stylesheet" href="/styles/shCoreDefault.css"/>
        <script type="text/javascript">SyntaxHighlighter.all();</script> 
    </head>

    The final script row in the snippet above is used to start the syntax highlighter.

    The actual UI is composed of a header with some document information and then an area where the document contents should be shown, using the syntax highlighter. This is how the body of the viewer.aspx looks like:

    <body>
        <form id="form1" runat="server">
            <div id="topdiv">
                <asp:HyperLink ID="hlSite" runat="server" Text="Site"/>  / 
                <asp:Label ID="lblDocument" runat="server" Text="Document.cs"/>
            </div>
            <div id="surface">
                <pre class="brush: csharp" style="overflow:auto;overflow-y:scroll">
                    <asp:Literal ID="litCode" runat="server" />
                </pre>
            </div>
        </form>
    </body>

    The topbar contains a Hyperlink control that we will use to link back to the library where the document resides and it also contains a Label control which will show the document name. The document area just contains a simple Literal control where we will output the C# file contents.

    Now onto the code behind implementation of this viewer.aspx, this is how it looks like:

    protected void Page_Load(object sender, EventArgs e)
    {
        string src = Request.QueryString["WOPISrc"];
                
        if (!String.IsNullOrEmpty(src))
        {
            string access_token = Request.Form["access_token"];
            string access_token_ttl = Request.Form["access_token_ttl"];
    
            // Get the metadata
            string url = String.Format("{0}", src);
            using (WOPIWebClient client =
                 new WOPIWebClient(url, access_token, access_token_ttl))
            {
                string data = client.DownloadString(url);
                JavaScriptSerializer jss = new JavaScriptSerializer();
                var d = jss.Deserialize<Dictionary<string, string>>(data);
                hlSite.NavigateUrl = d["BreadcrumbFolderUrl"];
                hlSite.Text = d["BreadcrumbFolderName"];
                lblDocument.Text = d["BaseFileName"];
            }
    
            // Get the content
            url = String.Format("{0}/contents", src);
            using (WOPIWebClient client = 
                new WOPIWebClient(url, access_token, access_token_ttl))
            {
                string data = client.DownloadString(url);
                litCode.Text = data;
            }
        }
    }

    The Page_Load method starts by retrieving the WOPISrc URL which is the URL that we will use to call back into the WOPI Server. The next thing it fetches is the access_token and access_token_ttl query form values – these are sent back with the request to the WOPI Server (as we discussed above).

    We are doing two calls to the WOPI Server. The first one, HTTP GET, using the exact WOPISrc URL to retrieve the metadata about the document. The WOPI Server returns a JSON construct that I convert into a Dictionary so that we can retrieve the name of the library/folder and its URL as well as the name of the document. There are several more properties that can be used, for full reference of this data structure see [MS-WOPI] 3.3.5.1.1.2.

    The second HTTP GET call uses a modified URL, we append “/contents”. This will return back the actual document data from the WOPI Server. Since a C# file is just a text file, we’ll just set that data to the Literal control.

    This ends the coding and configuring of our WOPI Client, let’s see if it works!

    Test it!

    To test it you must first register your WOPI Client, unless you did it before. If you have changed anything in the discovery xml, then you need to re-register it. See the previous post on how to register and unregister the WOPI bindings.

    Once the client is registered, just upload a C# (.cs) file to any document library and then click on the title of the file. You should be redirected (if you just registered or re-registered the bindings an IISRESET might be necessary) to the WOPI frame and start the WOPI client and it should look something like this:

    A C# WOPI Client

    Mission accomplished, isn’t that beautiful!

    Summary

    You have now over two posts seen how to build a C# Viewer for SharePoint 2013 (and Exchange 2013 and Lync 2013) implemented as a WOPI Client, according to the [MS-WOPI] protocol specification. There’s a few moving parts but I hope that I have helped you through all the quirks, and by “borrowing” this code you should be up and running pretty fast.

    I see a big opportunity window here for ISV’s or other software companies to build viewers and editors for their own document/data formats. I know there is a huge demand of products like this, for instance for drawings such as AutoCAD or why not a viewer for Photoshop PSD files?

    I have a plan on what to do with the code that I have been using to build these demos, watch this space for more info. But before that I’ll add some more features to our WOPI client in the near future.

  • SharePoint 2013: Building your own WOPI Client, part 1

    Tags: SharePoint 2013, WAC Server, WOPI

    Hi friends, finally time for some posts with some real code samples, and not some silly scripts. In this post, and a couple of follow up posts, I will walk you through the basics behind the WOPI protocol and WOPI Apps and WOPI Hosts. In the end you will see how we can create our own viewers and editors for files just like the WAC Server 2013 can view and edit Microsoft Office files in SharePoint 2013.

    What the heck is this WOPI stuff you are talking about?

    Before we look into all my beautiful code, let’s go through some of the basics about the new WOPI protocol. WOPI stands for Web Application Open Interface and is a protocol created and published by Microsoft to allow direct file communication between a WOPI App and a WOPI Host. You can find the details in the [MS-WOPI] specification. Well, it’s not much details yet, since it’s a working draft currently (version 0.2), but give it some time, or keep on reading these posts. Unfortunately since the specification is not yet done, things in this post might change in the future.

    This is taken directly from the specification:

    One example of how a client might use WOPI is by providing a browser-based viewer for a specific type of file. That client uses WOPI to get the contents of the file in order to present that content to the user as a web page in a browser. [MS-WOPI 1.3]

    I think it’s more easier to explain by using an example. For instance, take SharePoint 2013 and Office Web App Server 2013 (aka WAC Server). SharePoint 2013 is a WOPI Host (WOPI Server in the specification), it can use WAC Server, the WOPI App (WOPI Client in the specification), to allow the users to view and edit files. The end user will initiate a request to view a document from the browser to the WOPI Host, the WOPI Host will query the WOPI Client for it’s capabilities (this process is called discovery). The user will then ask the WOPI Client to show the file, The WOPI Client will talk to the WOPI Host (hosting the actual documents) and get information about the file and eventually the actual contents of the file, and then finally render it so that the client can see it.

    This whole process involves quite a few HTTP(S) round trips and all requests are signed and verified to make the process secure. WOPI is a protocol built on top of HTTP/HTTPS and the only allowed ports are 80/HTTP and 443/SSL. I recommend you to always use SSL for any kind of production environment, even though I in these posts use HTTP/80 – but that’s just to make it easier to debug using Fiddler.

    So what are we going to build…

    The WAC Server is a great and scalable implementation that allows users to work with Microsoft Office documents. But what about other document types, shouldn’t we have the opportunity to enjoy in-browser viewing and editing of those? Well, that’s exactly what this blog post series is about. I see this as a huge area where ISV’s can plug into the SharePoint world and build native viewers and editors. For instance, imagine if Autodesk took their web base AutoCAD editor AutoCAD WS and made that into a WOPI Client. All companies in the construction industry would dance in joy!

    But what about us developers, don’t you all store your C# files in SharePoint, and how would it be with a nice C# viewer and editor in SharePoint. Well, that can be fixed. Follow along and let’s build it.

    But, this ain’t for the cloud! You can only do these kind of amazing thing on real tin!

    The WOPI Client outline

    First of all, to build our WOPI Client we need to create a web site, hosted on port 80 or 443. This website will contain the WOPI discovery mechanism and it will have a ASP.NET forms page which acts as the viewer. In this sample we’ll make it a bit simpler for us and only allow HTTP/80 access to the client.

    Setting up the project

    IIS Debug settingsTo start with I create a new Visual Studio 2012 project using the Empty ASP.NET project template. This project is configured to be hosted in IIS for which I use a dedicated FQDN host header. This way it’s easier to host it on the same server as SharePoint during development.

    Implementing WOPI Discovery

    The first thing we need to do is to implement the WOPI Discovery mechanism. This mechanism is used by SharePoint 2013 (the Host) when you use the New-SPWOPIBinding cmdlet to connect to your client. That cmdlet will discover all features supported by the WOPI Client and depending on what parameters you give to the cmdlet it will register these bindings with SharePoint.

    The Discovery fileWOPI Discovery is implemented by making sure that your WOPI client responds to http://server/hosting/discovery. The client must return a data structure that describes the capabilities of the WOPI Client. This can be done using numerous methods. Here we’ll do a static implementation and just add a couple of folders and an XML file to the project.

    Since we want it to respond to /hosting/discovery we just update the web.config so that discovery.xml is a default document, like this:

    <configuration>
    ...
      <system.webServer>
        <defaultDocument enabled="true">
          <files>
            <add value="discovery.xml"/>
          </files>
        </defaultDocument>
      </system.webServer>
    </configuration>
    

    Now it’s time to actually define the WOPI operations in the discovery.xml file created. This is how the discovery must look like for our C# viewer:

    <?xml version="1.0" encoding="utf-8"?>
    <wopi-discovery>
      <net-zone name="internal-http">
        <app name="CSharp" 
             favIconUrl="http://wacky.corp.local/images/csharp.ico" 
             checkLicense="false">
          <action name="view" 
                  ext="cs" 
                  default="true" 
                  urlsrc="http://wacky.corp.local/csharp/viewer.aspx?&lt;theme=THEME_ID&amp;&gt;" />
        </app>
      </net-zone>
      <proof-key oldvalue="" value="BwIAA...1w=" />
    </wopi-discovery>

    The first we need to define in the discovery XML is the net-zone which specifies the intended network type. The name must match the zone you have configured SharePoint 2013 to use. Look it up by using the Get-SPWOPIZone cmdlet.

    Note: in a real world scenario you would build this XML dynamically and generate a net-zone block for each zone you would like to support. There must be at least one net-zone element and max four of them.

    In each net-zone you will define a set of app elements. An app has a name, an icon and optionally a requirement for a license check. Note that I’m hardcoding the URL’s here, you would never do that of course and generate this XML dynamically instead. In each app element the operations that app supports are specified using action elements. Each action has a name, that name must be one of the following (the WOPI specification is a bit kinder though and forces the WOPI servers to accept any values): view, edit, mobileview, present, presentservice, attendservice, attend, editnew, imagepreview, interactivepreview, formsubmit, formedit and rest. We use view here, since we’re building a viewer (to start with). For the usage of each name, please look into the WOPI Specification, or follow along in this blog post series. The ext attribute specifies the extension of the files, default specifies in this case that this is the default action for this app. Finally we have the urlsrc attribute. That attribute represents the URL to be used by the WOPI Host when using this action on the app. The URL has a set of replaceable parameters that can be used. In this URL I’ve specified THEME_ID to be able to have a light or dark theme on the viewer. There’s a few more parameters to use.

    [Updated 2012-09-30] Finally in this WOPI discovery XML we have the proof-key element. In the snippet above I’ve cropped the value attribute since it’s normally quite a long base64 encoded string. This element is so far not documented in the specifications but the value corresponds to a certificate key which is used to sign and verify responses from the WOPI client, to make sure no one tampered with the data. Typically you would like this value to be created when you install your WOPI client, but here’s the way to generate a static value for this demo (in PowerShell). Also this key should be re-generated at regular intervals, and therefore the oldvalue attribute. You’ll also see how this key is used in the next blog post.

    $crypt = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 2048
    $proof = [System.Convert]::ToBase64String($crypt.ExportCspBlob($false))
    $proofwithkey = [System.Convert]::ToBase64String($crypt.ExportCspBlob($true))
    $proof
    $proofwithkey
    

    Take the value from the $proof variable and add to the proof-key value attribute in the discovery.xml, and save the value from $proofwithkey – you’ll need that in the upcoming posts. Simply explained the proof-key sent from the client to the server during the discovery process allows the server to verify all signed requests from the client.

    image

    That’s it, the WOPI Discovery implementation is done.

    Register the WOPI Client with the SharePoint 2013

    Even though we have not created our view yet, we can verify that the discovery works by trying to connect the WOPI client to SharePoint (our WOPI Host). This is done by using the New-SPWOPIBinding cmdlet, like this:

    New-SPWOPIBinding -ServerName wacky.corp.local -AllowHTTP

    Two things to notice here, I must specify the FQDN of the server name, without any ports (remember only 80 and 443 is allowed) and by default it tries to use HTTPS. To allow HTTP communication I need to add the AllowHTTP parameter. Once you run this command it will return all available WOPI Bindings. Note that any errors in the discovery definition will be reported as invalid XML exceptions. If you have not connected SharePoint to a WAC Farm, then this should be your only binding and it should look like this.

    We got the binding to work!

    If it looks like above, then your golden!.

    In case you want to remove your WOPI bindings you just use the Remove-SPWOPIBinding cmdlet like this:

    Remove-SPWOPIBinding -Server wacky.corp.local

    Some final fixes before we test it…

    In the discovery XML we specified an icon and a viewer page. Let’s add them. I borrowed the icon from the Visual Studio templates and added that to an /images folder and I added an empty Web Form to the /csharp folder. Just add some random text to it so you know it’s your file. The project looks like this after the additions:

    Final project structure

    Test it!

    Now let’s fire up SharePoint, add a CS file to a document library. Then click on the file and the WOPI Client should be invoked and your viewer should be shown. Notice the icon and that the title of the page is the name of your file.

    And we see the viewer...

    If you receive the standard Download File dialog when clicking the file after you’ve done your WOPI Binding then execute an IISRESET and wait a minute or two, SharePoint needs to recover from all you awesomeness!

    Summary

    In this post we’ve gone through the basics in how WOPI Servers and WOPI Clients works as well as started our implementation of our very own WOPI Client. You’ve seen how the WOPI discovery process works and how to register the WOPI Client with SharePoint 2013.

    In the next part I’ll show you how to actually create the viewer by reading the file from the WOPI Server following the WOPI protocol standard.

  • How to use PowerShell to populate Active Directory with plenty enough users for SharePoint

    Tags: Active Directory, Windows Server 2008 R2, SharePoint

    When testing SharePoint or any other software that uses Active Directory or any kind of data storage it is important to test with lot of data, data with variations and real life data. One area that is often forgotten is Active Directory, ok you create 10 or 20 test users, perhaps 50 or 100 users called Mr. Test Testson32 or similar, but that is not enough. I like to use some real world data for my Active Directories both for testing and for sure it looks more fancy when doing a demo with SharePoint (especially with these new social features in SharePoint 2013). So I’m going to show you some of my scripts I use for this.

    Getting me some data

    imageFirst of all we need users. And not these test users called Test1, Test2, or you pet names (well I usually throw in my kids once in a while). One fantastic source of data is the Fake Name Generator. This amazing service can get you up to 50.000 randomly generated identities in bulk. You can choose the name sets, which countries they should come from and what properties you would like. Perfect for getting data that matches your clients! For this post I retrieved 25.000 users from Sweden, US etc and using both European and Chinese names! I chose to use the following properties; Given Name, Surname, Street Address, City, State, Postal Code, Country Abbreviation, E-mail, Username, Telephone, and occupation. All this gets emailed to me as a CSV files in just a couple of minutes.

    Importing the data

    Now on to the fun stuff with PowerShell. I’m going to take this CSV file import it into a PowerShell object, transform it a bit and then just create Active Directory accounts from them. Let’s start with some preparations.

    First of all I create a specific OU (“Demo Users”) to place all these accounts in, and I also set some password restrictions (well, this is a demo).

    Import-Module ActiveDirectory
    $dn = (Get-ADDomain).DistinguishedName
    $forest = (Get-ADDomain).Forest
    
    Set-ADDefaultDomainPasswordPolicy $forest -ComplexityEnabled $false -MaxPasswordAge "1000" -PasswordHistoryCount 0 -MinPasswordAge 0
    
    $ou = Get-ADOrganizationalUnit -Filter 'name -eq "Demo Users"'
    if($ou -eq $null) {
        New-ADOrganizationalUnit -Name "Demo Users" -Path $dn
        $ou = Get-ADOrganizationalUnit -Filter 'name -eq "Demo Users"'
    }

    Once this is done it’s time to start fiddling with the data. First of all I import the CSV file into a PowerShell object like this (of course you need to replace the file name with yours):

    $data = Import-Csv .\FakeNameGenerator.com_d7a08270.csv

    Then we’ll refine the CSV data into a new PowerShell structure, you can mix and fiddle with this as you like. Notice that my structure uses the parameter names of the New-ADUser cmdlet, so if you want to add cell phone and other attributes to your AD accounts, here’s the place to add them.

    $refineddata = $data | select  @{Name="Name";Expression={$_.Surname + ", " + $_.GivenName}},`
             @{Name="SamAccountName"; Expression={$_.Username}},`
             @{Name="UserPrincipalName"; Expression={$_.Username +"@" + $forest}},`
             @{Name="GivenName"; Expression={$_.GivenName}},`
             @{Name="Surname"; Expression={$_.Surname}},`
             @{Name="DisplayName"; Expression={$_.Surname + ", " + $_.GivenName}},`
             @{Name="City"; Expression={$_.City}},`
             @{Name="StreetAddress"; Expression={$_.StreetAddress}},`
             @{Name="State"; Expression={$_.State}},`
             @{Name="Country"; Expression={$_.Country}},`
             @{Name="PostalCode"; Expression={$_.ZipCode}},`
             @{Name="EmailAddress"; Expression={$_.EmailAddress}},`
             @{Name="AccountPassword"; Expression={ (Convertto-SecureString -Force -AsPlainText "WictorRocks!")}},`
             @{Name="OfficePhone"; Expression={$_.TelephoneNumber}},`
             @{Name="Title"; Expression={$_.Occupation}},`
             @{Name="Enabled"; Expression={$true}},`
             @{Name="PasswordNeverExpires"; Expression={$true}}

    As you can see I fix the Name and DisplayName properties and makes sure that the UPN uses the DNS name from the forest etc. I also enable all the users.

    And now all that is left is to add them to Active Directory! I don’t just add them to the OU created above, instead I actually create one OU for each Country – this makes it more easier to manage and also gives me an opportunity to test accounts in different OU’s. So here’s the snippet to add the users and create the other OU’s:

    $refineddata | % {
        $subou = Get-ADOrganizationalUnit -Filter "name -eq ""$($_.Country)""" -SearchBase $ou.DistinguishedName        
        if($subou -eq $null) {
            New-ADOrganizationalUnit -Name $_.Country -Path $ou.DistinguishedName
            $subou = Get-ADOrganizationalUnit -Filter "name -eq ""$($_.Country)""" -SearchBase $ou.DistinguishedName        
        }
        $_ | Select @{Name="Path"; Expression={$subou.DistinguishedName}},* | New-ADUser   
    }

    For 25.000 users this will run for a while, but it’s worth it!

    Note that you’ll get some errors while running this with lots of users normally. This is due to that some of the usernames are repeated. Of course with some handy PowerShell magic that can be fixed as well…

    And here’s the result

    If we now take a look in the Active Directory Users and Computers snap-in it should look something like this:

    OU's

    And then if we drill down into one of the OU’s there should be tons of users:

    Lotsa users

    All with nice details:

    An account

    Summary

    Now you’ve seen a very simple and fast way to generate lots of demo data for Active Directory. Of course you can modify the snippets above and adapt to your requirements. And you don’t need 25.000 users in your development environment remember it will take some time to sync and crawl with SharePoint…

  • Conference season, fall of 2012

    Tags: SharePoint, Presentations, Conferences

    Here we go again! The conferences are piling up one after another now when we have our new and shiny toy (=SharePoint 2013). For me personally this is an exciting time and gives me the opportunity to travel, meet old and new friends, to network and first and foremost learn more about SharePoint. A lot of us are currently experimenting with the beta bits, actively running some projects on it and just wondering how it will work when Microsoft finally will make the golden master. The conferences gives you the opportunity to hear about the latest news, learn about some features that you haven’t got a chance to explore yet, learn from those who has been working with this new product for months and even years and also hear a different perspective of others experiences compared to yours.

    I have three planned conferences for the next couple of months and a couple of user group meetings (where the interest has been tremendous!).

    SharePoint and Exchange Forum 2012 – October 22-23, Stockholm

    SEF2012http://www.seforum.se

    The first conference to kick off this season is the largest yearly SharePoint conference in Scandinavia – SharePoint and Exchange Forum. It’s hosted by Office 365 MVP and my good friend Göran Husman, and he has been doing this conference since the dinosaurs walked the earth. Last year I thought the conference really had a fantastic bunch of speakers and this year it will be even better! I will do two sessions this year (but why do I always get my slots the day after the big party, Beatrice!!!).

    Office Web Apps Server – all you need to know

    This is a session where we dig into the new Office Web Apps Server (or WAC server) to see how it works, how it’s configured etc. We’ll discuss why Microsoft decided to make it into its own server product and how it can integrate with other servers (except SharePoint) such as Exchange.

    SharePoint Apps – An introduction for developers

    This developer session will be an introductory SharePoint 2013 Apps development session. We’ll talk about why we would like to build Apps, the basic concepts and then of course build an app or two. My mate Eric Schupps will also do an App development session and we’ll make sure that you will benefit from visiting both of our sessions.

    SharePoint Conference 2012 – November 12-15, Las Vegas

    SPC12http://www.sharepointconference.com

    The SharePoint Conference 2012 is by far the largest conference this year and it will be the “coming out party” for SharePoint 2013. Everyone will be there – vice presidents, product group members, MCA’s, MCM’s, MVP’s…just everyone! Even though the Preview is out I expect to see some really cool stuff shown in the keynotes and sessions. I’m going there, and not speaking, with my great team from Connecta and we’ll do all that we can to suck in as much information as possible.

    SharePoint and Projects Conference Adriatics, November 28, Zagreb, Croatia

    SPC Adriaticshttp://spcadriatics.com

    This will be my first time at SPC Adriatics and I was invited by my good friend and SharePoint MVP Toni Frankola to speak here. This conference also has a great line-up of speakers and it’s going to be an intense day for everyone to learn the latest about SharePoint 2013. This time around I’ll do an upgrade/IT-Pro session.

    SharePoint 2013 – The upgrade story

    This session will cover what you need to do an upgrade from SharePoint 2010 to SharePoint 2013, You’ll learn the upgrade planning, methods and execution through a number of demos. During the session we will do a live upgrade from SharePoint 2010 to SharePoint 2013. We’ll also cover the Site Collection upgrade previews and health checks, which allows the site collection owner to determine when they are ready to move on to the new version.

     

    I hope to meet you out there on a conference shortly! Take care.

  • Sweden SharePoint User Group meetings in Stockholm and Malmö

    Tags: SharePoint, User Group, SSUG

    It’s been some time since we had some Sweden SharePoint User Group (SSUG) meetings. But now we’re back and more excited than ever. We’ll start with the first meeting in Stockholm the 24th of September and have another one coming the week after, the 4th of October, in Malmö.

    Stockholm, 24/9-21012

    This time our host is Steria AB. We will have two sessions, one delivered by Steria with a yet not announced session and another one by me which will be an introduction to SharePoint 2013.

    For more information and registration go to the event page at Eventbrite, we’re limited to 50 people, so get your ticket as soon as possible.

    Note: the event sold out in less than 20 hours. We still have a waiting list, so if you’re eager to attend sign up on the waiting list.

    Malmö, 4/10-2012

    The Malmö event will be hosted by Connecta. Two sessions will be delivered; one by Alfa Laval where they will share their experiences of one year with SharePoint. The second session will be an introduction to all the fancy new stuff in SharePoint 2013 by me.

    For more information and registration go to the event page at Eventbrite, we’re limited to 50 people, so get your ticket as soon as possible.

    I’m really looking forward to meeting all of you SharePointers in Sweden!

    And finally, we already have a couple of more planned User Group meetings with some really interesting guests, stay tuned…

    Update 2012-09-14: Make sure that you keep an eye on our website http://www.ssug.se and our Facebook page https://www.facebook.com/SharePointSweden

     

AWS Tracker

About Wictor...

Wictor Wilén is a Director and SharePoint Architect working at Connecta AB. Wictor has achieved the Microsoft Certified Architect (MCA) - SharePoint 2010, Microsoft Certified Solutions Master (MCSM) - SharePoint  and Microsoft Certified Master (MCM) - SharePoint 2010 certifications. He has also been awarded Microsoft Most Valuable Professional (MVP) for four consecutive years.

And a word from our sponsors...

SharePoint 2010 Web Parts in Action