Contents tagged with WAC Server

  • SPC14: Scripts for Mastering Office Web Apps 2013 operations and deployments

    Tags: Office Web Apps, Presentations, WAC Server, Exchange 2013, SharePoint 2013

    Here’s another post with scripts from my sessions at SharePoint Conference 2014 – this time from the Mastering Office Web Apps 2013 Operations and Deployments session (SPC383). To get a more in-depth explanation of all the details, please watch the recording at Channel 9.

    Let’s start…but first! OWA = Outlook Web App and WAC = Office Web Apps (Web Application Companion).

    Preparing the machine before installing Office Web Apps

    Before you install the Office Web Apps bits on the machine you need to install a set of Windows Features. The following script is the one you should use (not the one on TechNet) and it works for Windows Server 2012 and Windows Server 2012 R2.

    # WAC 2013 preparation for Windows Server 2012 (R2)
    Import-Module ServerManager
    
    # Required Features
    Add-WindowsFeature NET-Framework-45-Core,NET-Framework-45-ASPNET,`
        Web-Mgmt-Console,Web-Common-Http,Web-Default-Doc,Web-Static-Content,`
        Web-Filtering,Web-Windows-Auth,Web-Net-Ext45,Web-Asp-Net45,Web-ISAPI-Ext,`
        Web-ISAPI-Filter,Web-Includes,InkAndHandwritingServices,NET-WCF-HTTP-Activation45
    
    # Recommended Features
    Add-WindowsFeature Web-Stat-Compression,Web-Dyn-Compression
    
    # NLB
    Add-WindowsFeature NLB, RSAT-NLB
    
    
    Note, that I also add the NLB features here – it is not required if you use another load balancer.

    Starting a sysprepped WAC machine

    The following script is used when booting up a sysprepped machine that has all the Office Web App binaries installed (including patches and language packs), but no WAC configuration whatsoever. It will simply configure a NIC on the box and then join the machine to a domain and rename the machine. A very simple script that can be automated. Most of the scripts, just as this one, contains a set of variables in the beginning of the script. This makes it much more easier to modify and work with the scripts.

    $domain = "corp.local"
    $newName = "WACSPC2"
    $ou = "ou=WAC,dc=corp,dc=local"
    
    $ethernet = "Ethernet1"
    $ip = "172.17.100.96" 
    $prefix = 24
    $dns = "172.17.100.1"
    
    # Set IP
    Write-Host -ForegroundColor Green "Configuring NIC..."
    New-NetIPAddress -InterfaceAlias $ethernet -IPAddress $ip -AddressFamily IPv4 -PrefixLength $prefix 
    Set-DnsClientServerAddress -InterfaceAlias $ethernet -ServerAddresses $dns
    
    # Verify 
    ping 172.17.100.1
    
    # Get creds
    $credentials = Get-Credential -Message "Enter credentials with add computer to domain privilegies"
    
    # Join domain
    Write-Host -ForegroundColor Green "Joining domain..."
    Add-Computer -Credential $credentials -DN $domain -OUPath $ou 
    
    # rename
    Write-Host -ForegroundColor Green "Renaming machine..."
    Rename-Computer -NewName $newName
    
    # Reboot
    Write-Host -ForegroundColor Green "Restarting..."
    Restart-Computer

    Once this script is executed the machine should reboot and be joined to a domain.

    Configure a new Office Web Apps Farm

    Once the machine is joined to the domain it is time to configure Office Web Apps. If you want more information about the variables/parameters I use I recommend watching the session! These variables are solely for demo purposes and you should adapt them to your needs. Also this step requires that you have a valid certificate (pfx file) that conforms to the WAC certificate requirements.

    # New WAC Farm
    Import-Module OfficeWebApps
    
    $farmou = "WAC"                           # Note the format!!
    $internalurl = "https://wacspc.corp.local"
    $externalurl = "https://wacspc.corp.com"
    $cachelocation = "c:\WACCache\"           # %programdata%\Microsoft\OfficeWebApps\Working\d\
    $loglocation = "c:\WACLog\"               # %programdata%\Microsoft\OfficeWebApps\Data\Logs\ULS\
    $rendercache = "c:\WACRenderCache\"       # %programdata%\Microsoft\OfficeWebApps\Working\waccache
    $size = 5                                 # Default 15GB
    $docinfosize = 1000                       # Default 5000
    $maxmem = 512                             # Default 1024
    $cert = "wacspc.corp.local.pfx"              # File name
    $certname = "wacspc.corp.local"              # Friendly name
    
    
    $certificate = Import-PfxCertificate -FilePath (Resolve-Path $cert) -CertStoreLocation  Cert:\LocalMachine\My -ea SilentlyContinue 
    $certificate.DnsNameList | ft Unicode
    
    
    New-OfficeWebAppsFarm -FarmOU $farmou `
        -InternalURL $internalurl `
        -ExternalURL $externalurl `
        -OpenFromUrlEnabled `
        -OpenFromUncEnabled `
        -ClipartEnabled `
        -CacheLocation $cachelocation `
        -LogLocation $loglocation `
        -RenderingLocalCacheLocation $rendercache `
        -CacheSizeInGB $size `
        -DocumentInfoCacheSize $docinfosize `
        -MaxMemoryCacheSizeInMB $maxmem `
        -CertificateName $certname `
        -EditingEnabled `
        -Confirm:$false
        
        
    (Invoke-WebRequest https://wacspc1.corp.local/m/met/participant.svc/jsonAnonymous/BroadcastPing).Headers["X-OfficeVersion"]
    
    

    As a last step I do a verification of the local machine and retrieve the current Office Web Apps version.

    Create the NLB cluster

    In my session I used NLB for load balancing. The following scripts creates the cluster and adds the machine as the first node to that cluster. The script will also install the DNS RSAT feature and add two DNS A records for the internal and external names for the Office Web Apps Server. That step is not required and might/should be managed by your DNS operations team.

    # Create NLB Cluster
    $ip = "172.17.100.97"
    $interface = "Ethernet1"
    
    # New NLB Cluster
    New-NlbCluster -ClusterPrimaryIP $ip -InterfaceName $interface -ClusterName "SPCWACCluster" -OperationMode Unicast -Verbose
    
    # DNS Bonus
    Add-WindowsFeature  RSAT-DNS-Server  
    Import-Module DnsServer
    Add-DnsServerResourceRecordA -Name "wacspc" -ZoneName "corp.local" -IPv4Address $ip -ComputerName ( Get-DnsClientServerAddress $interface  -AddressFamily IPv4).ServerAddresses[0]
    ping wacspc.corp.local
    Add-DnsServerResourceRecordA -Name "wacspc" -ZoneName "corp.com" -IPv4Address $ip -ComputerName ( Get-DnsClientServerAddress $interface  -AddressFamily IPv4).ServerAddresses[0]
    ping wacspc.corp.com

    Adding additional machines to the WAC farm

    Adding additional machines to the WAC farm is easy, just make sure you have the certificate (pfx file) and use the following scripts on the additional machines:

    Import-Module OfficeWebApps
    
    $server = "wacspc1.corp.local"
    $cert = "wacspc.corp.local.pfx"  
    
    Import-PfxCertificate -FilePath (Resolve-Path $cert) -CertStoreLocation  Cert:\LocalMachine\My -ea SilentlyContinue 
    
    New-OfficeWebAppsMachine -MachineToJoin $server
    
    # Verify
    (Get-OfficeWebAppsFarm).Machines

    Configuring NLB on the additional WAC machines

    And of course you need to configure NLB and add the new WAC machines into your NLB cluster:

    $hostname = "WACSPC1"
    $interface = "Ethernet1"
    
    Get-NlbCluster -HostName $hostname | Add-NlbClusterNode -NewNodeName $env:COMPUTERNAME -NewNodeInterface $interface

    That is all of the scripts I used in the session to set up my WAC farm. All that is left is to connect SharePoint to your Office Web Apps farm

    Configure SharePoint 2013

    In SharePoint 2013 you need to add WOPI bindings to the Office Web Apps farm. The following script will add all the WOPI bindings and also start a full crawl (required for the search previews):

    The first part (commented out in this script) should only be used if your SharePoint farm is running over HTTP (which it shouldn’t of course!).

    asnp microsoft.sharepoint.powershell -ea 0
    
    # SharePoint using HTTPS?
    #(Get-SPSecurityTokenServiceConfig).AllowOAuthOverHttp
    #$config = Get-SPSecurityTokenServiceConfig
    #$config.AllowOAuthOverHttp = $true
    #$config.Update()
    #(Get-SPSecurityTokenServiceConfig).AllowOAuthOverHttp
    
    # Create New Binding
    New-SPWOPIBinding -ServerName wacspc.corp.local
    Get-SPWOPIBinding | Out-GridView
    
    # Check the WOPI Zone
    Get-SPWOPIZone
    
    # Start full crawl
    $scope = Get-SPEnterpriseSearchServiceApplication | 
        Get-SPEnterpriseSearchCrawlContentSource | 
        ?{$_.Name -eq "Local SharePoint Sites"}
    
    $scope.StartFullCrawl()
    
    # Wait for the crawl to finish...
    while($scope.CrawlStatus -ne [Microsoft.Office.Server.Search.Administration.CrawlStatus]::Idle) {
        Write-Host -ForegroundColor Yellow "." -NoNewline
        Sleep 5
    }
    Write-Host -ForegroundColor Yellow "."

    Connect Exchange 2013 to Office Web Apps

    In the session I also demoed how to connect Office Web Apps and Exchange 2013. The important things here to remember is that you need to specify the full URL to the discovery end-point and that you need to restart the OWA web application pools.

    # WAC Discovery Endpoint
    Set-OrganizationalConfig -WACDiscoveryEndpoint https://wacspc.corp.local/hosting/discovery
    
    # Recycle OWA App Pool
    Restart-WebAppPool -Name MSExchangeOWAAppPool
    
    # (Opt) Security settings
    Set-OwaVirtualDirectory "owa (Default Web Site)" -WacViewingOnPublicComputersEnabled $true -WacViewingOnPrivateComputersEnabled $true

    Summary

    I know I kept all the instructions in this blog post short. You really should watch the recording to get the full picture. Good luck!

  • Office Web Apps 2013: Excel Web App ran into a problem - not rendering Excel files

    Tags: Office Web Apps, WAC Server

    Introduction

    This is a story from the trenches where Excel Web App in Office Web Apps 2013 refuses to render Excel documents, while other Apps such as Word and PowerPoint works just fine. The end-users are met with the generic error message: “We’re sorry. We ran into a problem completing your request.”

    Houston - we got a problem

    The problem is easy to solve but can be somewhat difficult to locate and in this post I will show you how to find the issue and fix it.

    Symptoms

    Whenever Office Web Apps 2013 fails to render a document it shows the end-users a generic error message without any details. Fortunately the Office Web Apps Server contains good logging mechanisms and will in most cases give you an idea on where to solve it and in some cases it’s written in clear text.

    This specific issue, for the Excel Web Apps, shows itself in three different places (except for the error message that is shown in the user interface). First of all “normal” sys admins will see a couple of errors in the System Event Log manifesting itself like this:

    System log

    Event Id 5011:

    A process serving application pool 'ExcelServicesEcs' suffered a fatal 
    communication error with the Windows Process Activation Service. 
    The process id was '2168'. The data field contains the error number.

    Event Id 5002:

    Application pool 'ExcelServicesEcs' is being automatically disabled due to a series 
    of failures in the process(es) serving that application pool.
    

     

    Pretty nasty messages which does not give you a clue, except that something is horribly wrong. There are also lots of Dr Watson log entries in the Application log which might cause the admin to start looking up the Microsoft support phone number.

    The more “clever” admin then knows that Office Web Apps actually has it’s own log in the Event Viewer. When checking that log messages like the following are shown for the Excel Web App:

    Event Id 2026:

    An internal error occurred.
       at System.Diagnostics.PerformanceCounterLib.CounterExists(String machine, String category, String counter)
       at System.Diagnostics.PerformanceCounter.InitializeImpl()
       at System.Diagnostics.PerformanceCounter..ctor(String categoryName, String counterName, String instanceName, Boolean readOnly)
       at System.Diagnostics.PerformanceCounter..ctor(String categoryName, String counterName, Boolean readOnly)
       at Microsoft.Office.Excel.Server.CalculationServer.ExcelServerApp.Initialize()
       at Microsoft.Internal.Diagnostics.FirstChanceHandler.ExceptionFilter(Boolean fRethrowException, 
          TryBlock tryBlock, FilterBlock filter, CatchBlock catchBlock, FinallyBlock finallyBlock)

    This should actually start to give you an idea – something is wrong with the Performance Counters on this machine. Worst thing to do here is to start fiddling with the registry and try to fix it or start adding users/groups into the performance counter groups.

    The “smartest” Office Web Apps admin then takes a look at the Trace Logs (ULS) (and that admin most likely read my SharePoint post “The Rules of SharePoint Troubleshooting” – if not, he/she should!). This is what will be found:

    Excel Web App                 	Excel Calculation Services    	cg34	Unexpected	Unexpected exception occured 
      while trying to access the performance counters registry key. Exception: System.InvalidOperationException: Category does not 
      exist.     at System.Diagnostics.PerformanceCounterLib.CounterExists(String machine, String category, String counter)     at ...
    Excel Web App                 	Excel Calculation Services    	89rs	Exception	ExcelServerApp..ctor: An unhandled exception 
      occurred during boot. Shutting down the server. System.InvalidOperationException: Category does not exist.     at 
      System.Diagnostics.PerformanceCounterLib.CounterExists(String machine, String category, String counter)     at 
      System.Diagnostics.PerformanceCounter.InitializeImpl()     at ...
    Excel Web App                 	Excel Calculation Services    	89rs	Exception	...atchBlock, FinallyBlock 
      finallyBlock) StackTrace:  at uls.native.dll: (sig=4635455b-a5d6-499c-b7f2-935d1d81cf8f|2|uls.native.pdb, offset=26E32) at 
      uls.native.dll: (offset=1F8A9)	 
    

    The key thing here is the “Category does not exist” message.

    When the Excel Web App Calculation Services is starting (and the Excel Calc Watchdog) it is trying to read a performance counter. If that performance counter is not found – it will just crash!

    Unfortunately there is no good way to find out which performance counter it is trying to use, except firing up good ole Reflector. Using that tool we can find that it is trying to access an ASP.NET performance counter.

    Resolution

    The fix for the problem is easy – we just need to register/update the performance counters for ASP.NET. This is done using the lodctr.exe tool like this:

    lodctr C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_perf.ini

    Give it a few seconds and then retry to load an Excel file using Office Web Apps and all your users should once again be happy.

    Summary

    A simple fix to an annoying problem, which could be difficult to locate unless you know where to look (and in this case also have the skillz to read some reflected code).

    This error might not be so common, but it shows the importance of having a correctly installed machine and that you shouldn’t go fiddling with settings or the registry if you’re not really sure on what you’re doing – ok, not even then…

  • Office Web Apps Server: Which version is installed?

    Tags: Office Web Apps, WAC Server

    If you have been working with SharePoint you should know by now how to get the build version of an installation using PowerShell. Knowing the version of the installation is crucial for troubleshooting and knowing what features or limitations the current installation has, given the new release cadence. If you don’t know how to do it then Bing for it and then return here. But how do you do the same for Office Web Apps Server 2013?

    Retrieve the version number of an Office Web Apps Server installation

    Knowing the current version for Office Web Apps Server 2013 (WAC) is also important to know. Just as with SharePoint new features and bugs can and will be introduced over time and you need to know the version to be able to correctly get support and to patch it. Unfortunately there is not such an easy approach as with SharePoint – we cannot use the WAC cmdlets to get the current build version.

    Instead we can rely on another PowerShell method – Invoke-WebRequest. Hmm, that has nothing to do with WAC you might be thinking, which is true. But Office Web Apps returns the current version as an HTTP Header – the X-OfficeVersion header.

    In order to do use this cmdlet and to invoke an HTTP request we also need to send an anonymous request, to avoid 401’s. For this we can use one of the anonymous end-points that Office Web Apps Server exposes, for instance the end-point for the broadcast ping, like this:

    (Invoke-WebRequest https://wac.contoso.com/m/met/participant.svc/
    jsonAnonymous/BroadcastPing).Headers["X-OfficeVersion"] 

    As you can see we request the endpoint, and retrieve the specific version header from the response. This will return the current build version of your WAC farm, for instance “15.0.4535.1000” which is the August 2013 hotfix for WAC.

    Known version numbers

    I have collected the known version numbers of Office Web Apps Server 2013 on this page http://www.wictorwilen.se/WACVersions. I will try to continuously update it as the WAC server retrieves new patches and upgrades. As a bonus I also added version numbers for the version that SkyDrive currently uses (interesting!).

  • Office Web Apps Versions

    Tags: Office Web Apps, WAC Server, WAC, SkyDrive, OneDrive, Office Online, OneNote

    This page contains the versions for Office Web Apps Server/Office Online (aka WAC Server) both for the on-premises version and the cloud version used in Office 365 and SkyDrive/OneDrive.

    Office Web Apps Server 2013 (on-premises)

    Version number retrieved using the following method:

    (Invoke-WebRequest https://wac.contoso.com/m/met/participant.svc/jsonAnonymous/BroadcastPing).Headers["X-OfficeVersion"]

    Name Version KB
    RTM 15.0.4420.1007
    March 2013 Public Update (1) 15.0.4481.1005 KB2760445
    April 2013 Update 15.0.4481.1508 KB2810007
    August 2013 Hotfix 15.0.4535.1000 KB2817521
    October 2013 Cumulative Update (2) 15.0.4551.1003 KB2825686
    November 2013 Update (3) 15.0.4551.1005 KB2837634
    December 2013 Hotfix 15.0.4551.1508 KB2850013
    January 2014 Security Update (MS14-001) 15.0.4551.1515 KB2863879
    Service Pack 1 Preview 15.0.4569.1001 n/a
    Service Pack 1 15.0.4569.1506 KB2817431

    Version notes

    1. Introduced new WOPI bindings, most significantly for WordPdf
    2. Removal of one WOPI binding; mobileView for WordPdf removed
    3. Includes fix for reported event log id's 1009 and 1150

    OneDrive/SkyDrive/Office 365 (cloud)

    This are found version numbers of the public SkyDrive, which uses Office Web Apps Server. The version number is retrieved using this PowerShell command:

    US

    (Invoke-WebRequest -Uri https://powerpoint.officeapps.live.com/p/ppt/view.svc/jsonAnonymous/GetSlide -Method POST).Headers["X-OfficeVersion"]

    Date detected
    Version
    2013-10-15 16.0.1727.1037
    2013-10-17 16.0.1727.1038
    2013-10-30 16.0.1727.1041
    2013-11-07 (1) 16.0.2130.3000
    2013-11-08 16.0.2130.3002
    2013-11-20 16.0.2130.1021
    2013-12-11 16.0.2130.1029
    2014-01-13 16.0.2130.1035
    2014-01-22 (2) 16.0.2430.3006
    2014-01-27 16.0.2430.3106
    2014-01-28 16.0.2430.3008
    2014-01-31 16.0.2430.1009
    2014-02-05 16.0.2430.1010
    2014-02-16 16.0.2430.1013
    2014-02-16 (3) 16.0.2430.3014
    2014-03-12 16.0.2430.1019
    2014-03-14 16.0.2430.3019
    2014-03-19 16.0.2430.1022
    2014-03-28 16.0.2430.3024
    2014-04-03 16.0.2430.1026
    1. Roll out of new real-time co-authoring: Collaboration just got easier: Real-time co-authoring now available in Office Web Apps 
    2. Big update to the user interface
    3. Excel Web App now mentioned as Excel Online in the UI (OneDrive service went live as well)

    EUC

    (Invoke-WebRequest -Uri https://euc-powerpoint.officeapps.live.com/p/ppt/view.svc/jsonAnonymous/GetSlide -Method POST).Headers["X-OfficeVersion"]

    Date detected
    Version
    2013-11-07 16.0.1727.1041
    2013-11-08 16.0.2130.3002
    2013-11-20 16.0.2130.1021
    2013-12-11 16.0.2130.1029
    2014-01-13 16.0.2130.1035
    2014-01-22 16.0.2430.3006
    2014-01-27 16.0.2430.3106
    2014-01-28 16.0.2430.3008
    2014-01-31 16.0.2430.1009
    2014-02-05 16.0.2430.1010
    2014-02-16 16.0.2430.1013
    2014-02-16 16.0.2430.3014
    2014-03-12 16.0.2430.1019
    2014-03-14 16.0.2430.3019
    2014-03-19 16.0.2430.1022
    Stopped tracking

    OneNote.com

    (Invoke-WebRequest -Uri https://www.onenote.com -Method POST).Headers["X-OfficeVersion"]

    Date detected
    Version
    2014-01-31 16.0.2524.3000
    2014-02-11 16.0.2606.1000
    2014-02-13 16.0.2610.1000
    2014-02-18 16.0.2611.1000
    2014-03-12 16.0.2707.3000
    2014-03-14 16.0.2710.3004
    2014-03-19 16.0.2714.1000
    2014-03-28 16.0.2721.1000

  • Office Web Apps 2013: Securing your WAC farm

    Tags: WAC Server, Office Web Apps, SharePoint 2013, Security

    With this new wave of SharePoint, the Office Web Apps Server (WAC – I don’t like the OWA acronym, that’s something else in my opinion) is its own server product, implementing the WOPI client protocol, which allows a client to retrieve documents from SharePoint on the behalf of the user. Documents will flow from the WOPI servers (SharePoint, Lync, Exchange etc.) to the Office Web Apps Server – this means that potentially confidential information will be transferred from the SharePoint environment and stored/cached on another server. This could result in unnecessary information leakage and compromise the enterprise security.

    In this post I will walk through a number of steps that you can do to properly secure your Office Web Apps 2013 farms. And you should seriously consider and implement most of these methods.

    Note: this post focuses on the Office Web Apps Server and not a WOPI client in general (but if you’re building your own you should consider security as well!).

    The WOPI protocol specification and security

    Note: I will not cover how WOPI clients and servers implements the server to server authentication and authorization.

    WAC runs as Local System

    To start with it is very important to know that the Office Web Apps Server 2013 runs as the Local System and Network Service on the machine it is installed on. There is no service account or anything! This means that you cannot protect your systems using dedicated accounts etc., like you do with SharePoint, SQL and other applications.

    The images below shows the Office Web Apps Windows Service, which runs as LocalSystem.

    Local System

    And this image shows some of the applications pools in the IIS on an Office Web Apps machine.

    Network Service

    The advantage of using these local accounts is that it makes installation and configuration easier. But it is very important that you are aware of this configuration.

    SSL is a requirement!

    Exposing the Office Web Apps Server over HTTPS should be a requirement in my opinion. There is no reason not to. Having it on HTTP will only cause trouble for you; for instance if your SharePoint uses https you will not be able to render the iFrame containing the document (aka WOPI Frame) since you’re not allowed to show http content in an https environment. But first and foremost you’re sending data in clear text.

    So what about SharePoint on HTTP then? Well, if you’re using SharePoint 2013 you should seriously consider running that over HTTPS as well – that IS a best practice. SharePoint 2013 leverages several technologies that sends tokens and credentials over the wire, OAuth for instance, so in order to have a secure environment make sure you use HTTPS for SharePoint as well. If you are running SharePoint on HTTP you must fiddle with the security settings in SharePoint to allow OAuth over HTTP – and this is not a good thing.

    Certificates are king!

    Any WAC farm running on SSL must have a certificate for the HTTPS endpoint. You can use self-signed, issue certificates using a Domain CA or buy a certificate. When you’re creating the WAC farm, using New-OfficeWebAppsFarm, you can/should specify the certificate.

    For any SharePoint, WAC and even SQL installations nowadays certificates are more and more important. If you’re on the verge of deploying these in your organization you should consider deploying a Domain CA – which is not a lightweight task.

    Securing the communication using IPSec

    If you for some reason do not run HTTPS on SharePoint and/or WAC you could consider implementing IPSec. Unfortunately there is no button in the Control Panel that says “Use IPSec”. This is something that requires careful planning and testing. So going SSL might be an easier way. But consider the scenario where you have an internet facing web site which leverages WAC and using the HTTP protocol – then you should consider using IPSec for the communication between SharePoint and Office Web Apps Server.

    Firewall considerations and requirements

    When setting up your Office Web Apps Farm you should also configure the firewall for the WAC machines. Office Web Apps uses four different ports. It uses 80 and 443 for HTTP and HTTPS, that’s used by the end-users and the WOPI Server/Client communication. Internally Office Web Apps uses port 809 (HTTP) and 810(HTTPS) for communication between the WAC machines. I’ve only seen 809 in use, which is the default. There is no way (I’ve found at least, but internally WAC has a switch to use port 810) to configure WAC to use port 810 and if you do find a way, it’s likely unsupported. The things sent over the wire using the admin channel (809) is mainly health and configuration information for the WAC farm, but it would be nice to be able to secure this channel as well (IPSec!).

    When installing WAC the Windows firewall is configured to allow incoming TCP connections on port 80, 443 and 809.

    WAC Windows Firewall Rule

    As always it is a good practice to evaluate these default rules and if you’re not using port 80, disable that port. For port 809 it might also be a good practice to make sure that it only allows incoming connections if they are secure (i.e. implement IPsec).

    Even more secure...

    Preventing rogue machines

    So far we’ve been talking about how to secure information being transmitted from and to the Office Web Apps farm. Let’s take a look at Office Web Apps farm security from another angle. Joining a new WAC machine to an Office Web Apps Farm can be quite easy. The only thing that you need is local administrator access on the WAC machine that is the master (the Get-OfficeWebAppsMachine gives you the master machine). Depending on how you’re having your (virtual) metal hosted this might be a problem, too many sysadmins have to much permissions out there. If you have this access then you can easily join a rogue machine to the WAC farm and take control over it, without the users/client knowing anything about it.

    There are a couple of methods you can and should use to protect the WAC farm. And the error messages below can also be a good troubleshooting reference…

    Master Machine Local Administrator

    If the account trying to create the new WAC machine does not have local admin access on the machine specified when joining the WAC farm you will simply get an “Access is denied”.

    New-OfficeWebAppsMachine : Access is denied

    As a side note; if you’re not running the cmdlet using elevated privileges you will get an “You must be authenticated as a machine administrator in order to manage Office Web Apps Server”.

    Using the Firewall

    I already mentioned the firewall. If the machine joining the WAC farm cannot access the HTTP 809 channel the New-OfficeWebAppsMachine will throw a “The destination is unreachable error”.

    The destination is unreachable error

    This is a fairly easy way to protect the farm, but if the user has local admin access on the master machine it can easily be circumvented.

    Certificate permissions

    If you’re using a domain CA, make sure that you protect the private key using ACL’s, or if you’re buying a certificate make sure to store the certificate private key in a secure location. If you’ve specified a certificate when the Office Web Apps farm was created, which you should have, then the user cannot join the new machine – regardless of local machine admin, since the user cannot install the certificate locally. The error message shown is “Office Web Apps was unable to find the specified certificate”.

    Office Web Apps was unable to find the specified certificate

    Using an Organizational Unit in Active Directory

    The way that Microsoft recommends to secure your WAC farm is to have a dedicated OU in Active Directory where the computer accounts for the WAC farm is located. When joining a new machine to the farm the cmdlet verifies that the account is in the OU specified by the WAC configuration. If it’s not then you’ll see the “The current machine is not a member of the FarmOU”.

    The current machine is not a member of the FarmOU

    The Farm OU is specified when creating a new WAC farm or using the Set-OfficeWebAppsFarm/ cmdlet. The only caveat with this OU is that it has to be a top level OU in Active Directory. Creating that OU in your or your customers AD might cause some headache, but if you want to use the FarmOU as protection method for your farm it has to be this way. That’s the way it is designed!

    Also having all the WAC servers in a OU gives you other benefits; such as using Group Policies to control the WAC servers.

    Limit the WOPI Server and host access

    Now we’ve seen how we protect the farm from rogue machines and data tampering. Another issue with the WAC farm in it’s default configuration is that any WOPI Server can use it. Might not be a big problem for most of the internal installations, but what if you’ve designed a WAC farm and someone with a huge SharePoint collaboration implementation connects to your WAC farm. It can sure bring it down. Or if you’re exposing your Office Web Apps farm on the internet anyone on the internet can potentially use it.

    For this purpose there’s a cmdlet called New-OfficeWebAppsHost. This cmdlet allows you to specify host names that will be accepted by the WAC farm. The cmdlet interprets any domain with a wildcard. For instance the following cmdlet will allow all WOPI Servers on contoso.com (www.contoso.com, extranet.contoso.com etc.) to contact the WAC farm:

    Set-OfficeWebAppsHost -Domain "contoso.com"

    Do not forget to do this!!

    Summary

    You’ve seen quite a few ways how to protect your WAC farm from information leakage, rogue machines, undesired excessive usage etc. Using HTTPS and certificates together with a dedicated OU in Active Directory will give you the most secured WAC Farm. Hopefully you also understand a bit more on how Office Web Apps Server works internally. It’s a magnificent and simple server product, but it should be handled with care. 

  • SharePoint 2013: Building your own WOPI Client, part 4, now Search enabled

    Tags: SharePoint 2013, WOPI, WAC Server

    Well, I thought I should write another episode of my Building your own WOPI Client series, here’s the links to the previous episodes part 1, part 2 and part 3. This time around we’ll dig into another of the different actions that a WOPI Client can surface – the interactivepreview mode.

    Background

    As you’ve seen in the previous posts we can build a viewer and an editor for C# files, to be used in document libraries for instance. What if we would like to lift up and enhance our custom file formats in search, just like Office Web Apps Server does with the Office files. We’ll you can do that very easy, and you should! In this post I’ll show you how to surface the preview mode in a Search flyout. This also means that we’re going to take a look at the new SharePoint 2013 Search Engine, the Design Manager and some funky HTML syntax.

    Configure indexing for C# files

    The first thing we need to do is to actually make SharePoint indexing our C# (.cs files). This is done in two steps. First we need to tell SharePoint 2013 Search to include the .cs file extension and then we need to tell the search engine what filter to use. To include .cs files in the index there is two ways to do it, using PowerShell or using the Central Administration. In Central Administration you go to the Search Service Application and choose File Types, then click on New File Type and type in cs. That’s it. Using PowerShell the following snippet works (as long as you only have one SSA):

    $ssa = Get-SPServiceApplication | ?{$_.TypeName -eq "Search Service Application"}
    New-SPEnterpriseSearchCrawlExtension -name cs -SearchApplication $ssa

    Once that is done we need to fiddle a bit with the registry (usual regedit disclaimers apply here). Start regedit.exe and navigate to HKLM\SOFTWARE\Microsoft\Office Server\15.0\Search\Setup\ContentIndexCommon\Filters\Extensions and choose to add a new Key. Give the key the name “.cs” (without quotations) and then set the default value to the following (fancy crafted) Guid: {0FF1CE15-002C-0000-0000-000000000000}. All you now have to do is to restart the Search Engine like using the PowerShell below and then start a full crawl:

    Restart-Service OSearch15

    Now when you search for any content in your .cs files it should look something like this:

    Boring search result

    It’s something, but not what we want…

    Building the WOPI Preview

    Before we start customizing the UI let’s build the interactiepreview mode of the WOPI client. Just as we did in the previous post, when we added the edit mode, we need to add a new action to the discovery file and then remove and add the bindings again (for that PowerShell, see previous post). This is the action I’ve added to my WOPI Client:

    <action name="interactivepreview"
            ext="cs"
            default="false"
            urlsrc="http://wacky.corp.local/csharp/preview.aspx?&lt;ui=UI_LLCC&amp;&gt;&lt;rs=DC_LLCC&amp;&gt;" />

    As you can see that the urlsrc points to a preview.aspx web form. That web form is essentially a copy of the viewer.aspx, with the exception of the menu which I have removed. In a real world scenario you might consider having very different viewers for the normal view mode and the interactivepreview mode. You want this process to be fast and you might just generate a thumbnail or something.

    When all is compiled and deployed, and you have removed and re-added the WOPI bindings, it’s time to hook this up to search.

    Creating the Display Templates

    Finding the Design ManagerLet’s build the search interface. The SharePoint 2013 Search Interface does not any longer require extensive XSLT skills but instead plain ol’ HTML hacking. Every Result Type in SharePoint 2013 Search can have it’s own Display Templates, and there’s two of them; the normal item view and the flyout/hover view. The easiest way to create new Display Templates is to copy an existing one. For this sample let’s use the Word Display Templates.

    You will find the Display Templates in the new Design Manager (which exists in a Search Center or a site with the Publishing features enabled). You access the Design Manager through the Site Settings menu. Once in the Design Manager you should choose Display Templates in the menu on the left hand side. Then I usually filter on Target Control Type, to narrow down on the Display Templates used for Search; choose SearchResults and SearchHoverPanel. Now scroll down to the Word Item and Word Hover Panel. You’ll see two of each but we’re only interested in the HTML files for now. Download those two files to your local disc and name them Item_Cs.html and Item_Cs_HoverPanel.html respectively.

    The Word Display Templates

    Now open these two files in your favorite HTML tool – for me that is Visual Studio 2012. There’s a lot of weird stuff in here as you will notice, lots of comments and JavaScripts. I’m not digging into all the details here, that’s for Randy Drisgill or someone else to do.

    Let’s do some simple string replacement. In both files replace “Word” with “Cs” as the first thing. Next update the mso:MasterPageDescription to something more appropriate and the title tag, perhaps replace “Microsoft Word” with “C#”. In the Item_Cs.html file  locate the second div having an id equal to "_#= $htmlEncode(itemId) =#_" and remove everything inside it and replace with this instead:

    <div class="ms-srch-item-title">
      <h3 class="ms-srch-ellipsis">
        <a class="ms-srch-item-link" href="_#= ctx.CurrentItem.Path =#_">_#= ctx.CurrentItem.Title =#_</a>
      </h3>
    </div>
    <div class="ms-srch-item-body">
       _#= Srch.U.processHHXML(ctx.CurrentItem.HitHighlightedSummary) =#_
    </div>
    <div id="_#= $htmlEncode(hoverId) =#_" class="ms-srch-hover-outerContainer"></div>

    What this does is that we’re first rendering the title of the document with the value ctx.CurrentItem.Title. All these JavaScript variables are enclosed in __#= … =#_. Then we output the hit highlighted summary using a built-in method (Srch.U.processHHXML). Finally there’s an empty div which will contain the flyout (The flyout is defined in the Item_Cs_HoverPanel.html file). There is one final thing that we need to do and that is to update the hoverUrl  JavaScript variable in this Item_Cs.html file. It should point to the Item_Cs_HoverPanel.js file like this:

    var hoverUrl = "~sitecollection/_catalogs/masterpage/Item_Cs_HoverPanel.js";

    Notice that it points to a JS file, not the HTML file. And also that it’s located directly in the masterpage gallery and not in a subfolder (compared to the original Word html file).

    The Item_Cs_HoverPanel needs no more changes than replacing Word with Cs and updating the title and description.

    To add these Display Templates to the Design Manager, just drag and drop them into your browser where you downloaded the original Display Templates. The files will end up as Draft version so go ahead and publish both of the HTML files.

    The Display Templates

    Once the files are approved, the Design Manager will create two JS files from these HTML files and that’s what’s used behind the scenes when rendering the search results.

    Create a Result Type

    The final thing we have to do is to tell the Search Center to actually use our newly crafted Display Templates when finding C# files. For that we need to create a new Result Type. Go to Site Settings and choose Search Result Types under Site Collection Administration. Click on New Result Type to start creating it. Give the Result Type an appropriate name, for instance “C# Files”. Under Conditions choose Show more conditions and select the FileExtension property and set it so that it “Equals any of…” and enter “cs” in the text box. Under Actions choose the C# item in the drop down and then simply click Save. You should now have a Result Type looking like this:

    The C# Result Type

    Test the stuff…

    All that is left is now to test if all this works. Go to the search page of you Search Center and search for something you know exists in your C# files, that by now should have been indexed. You should then see something like this:

    The WOPI Client in the search result

    You see the items (using the Item_Cs Display Template) and if you hover over one of the search results then you see the preview from the WOPI Client being generated (using the Item_Cs_HoverPanel). Isn’t that nice, huh?

    Summary

    In this post you’ve seen yet another way to leverage a WOPI Client, this time in search, using a preview mode of the files. You have also seen how “easy” it is to generate a custom search result design using the Design Manager, Display Templates and Result Types. I’m really looking forward to seeing WOPI Clients for all kinds of different formats out there…

  • 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.

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