How to grant farm-wide read access to a user in Sharepoint 2013

A few weeks ago I was asked to grant a particular user read rights to any resources of a Sharepoint farm; sites, folder, lists, etc…, irrespective of the permission currently set on those resources (i.e. possible unique permissions).

By the way, it appears that I’m also a particularly skilled Sharepoint administrator. It apparently happened over night, without me even noticing.

Unable to find any out-of-the-box solution for my particular situation, I came out with a bunch of scripts that did the trick and I think are worth sharing. Of course, the work I did has been mainly taping together snippets and scripts that I found somewhere else so the credit goes mostly to the Internet, as usual. There is a bit of original work, though.

The basic idea was to give the rights to a new group, GlobalReaders, created for the occasion in each root site, and then add the users to the group as needed. In the future, the users will be easily removed and the rights revoked, if needed.

1. Automating the creation of the GlobalReader group

If the farm has a large number of web applications and site collections, it can be hard to manually create the group. So the first step is automating this task. The following script scans all the sites of the farm and adds the desired group to their root web

function Add-GlobalReadersGroupsToAllSites
{
    $sites = Get-SPSite -Limit ALL
    Write-Host $sites
    foreach($site in $sites)
    {
        Write-Host "Adding GlobalReaders group to " $site.Url
        $site.RootWeb.SiteGroups.Add("GlobalReaders", $site.Owner, $site.Owner,
            "Members of this group has read rights on any resources of the farm (sites, lists, folders, etc...)")
        $site.RootWeb.Update()
    }
}

2. Assigning a group read rights on an SPWeb

The first brick of the procedure is a function that takes the name of a group and an SPWeb URL, and assign the group read rights over the Web itself. The script also takes into account folders and lists whose permission inheritance has been broken (unique permission).


# Assigns Read permission to a list of users for the Web provided
function Grant-ReadPermToGroup
{
    if($args.Length -lt 2)
    {
        Write-Error "Grant-ReadPermToUser requires 2 parameters. Usage: Grant-ReadPermToUser <SubSiteUrl> <GroupName>"
    }

    # Write-Host $args[0] " " $args[1]

    $web = Get-SPWeb -Identity $args[0]
    $site = $web.Site
    $groupList = $web.SiteGroups | Where { $_.Name -eq "GlobalReaders" }
    $readerGroup = $groupList[0]

    Write-Host "Assigning read permission for Web " $web "($($web.Url))"
    Write-Host "Group:"

    Write-Host "    "$readerGroup.Name

    Write-Host "Granting read permission to $readerGroup"
    $assignment = New-Object Microsoft.SharePoint.SPRoleAssignment($readerGroup)
    $role = $site.RootWeb.RoleDefinitions.GetByType([Microsoft.SharePoint.SPRoleType]::Reader)

    $assignment.RoleDefinitionBindings.Add($role);
    Write-Host $assignment
    $web.RoleAssignments.Add($assignment)

    # #########################################################################
    # Now let's deal with lists and folders with unique permission assignments

    # Finds all the lists of the given web that have unique role assignments
    # (broken inheritance)
    $uniqueAssignList = $web | select -ExpandProperty Lists |
        Where { -not $_.Hidden -and $_.EntityTypeName -ne "PublishedFeedList" -and $_.HasUniqueRoleAssignments}

    foreach($l in $uniqueAssignList)
    {
        Write-Host "Grantig read permission for list " $l.Title
        # Assign read permission
        # $role = $site.RootWeb.RoleDefinitions.GetByType([Microsoft.SharePoint.SPRoleType]::Reader)
        $role = $l.ParentWeb.RoleDefinitions.GetByType([Microsoft.SharePoint.SPRoleType]::Reader)
        Write-Host "Role: " $role
        $assignment = New-Object Microsoft.SharePoint.SPRoleAssignment($readerGroup)
        $assignment.RoleDefinitionBindings.Add($role)

        $l.RoleAssignments.Add($assignment)
    }

    # Finds all the non-hidden folder (at any level)
    # of the given web that have unique role assignments
    # (broken inherintance)
    $uniqueFolders = $web |
        select -ExpandProperty Lists |
        Where { -not $_.Hidden -and $_.EntityTypeName -ne "PublishedFeedList"} |
        select -ExpandProperty Folders |
        Where { $_.HasUniqueRoleAssignments -and -not $_.Hidden } 

    foreach($f in $uniqueFolders)
    {
        Write-Host "Grantig read permission for folder " $f.Title
        # Assign read permission
        # $role = $site.RootWeb.RoleDefinitions.GetByType([Microsoft.SharePoint.SPRoleType]::Reader)
        $role = $f.ParentList.ParentWeb.RoleDefinitions.GetByType([Microsoft.SharePoint.SPRoleType]::Reader)
        Write-Host "Role: " $role
        $assignment = New-Object Microsoft.SharePoint.SPRoleAssignment($readerGroup)
        $assignment.RoleDefinitionBindings.Add($role)

        $f.RoleAssignments.Add($assignment)
    }

    Write-Host "     OK! (^__^)"
}

Now we can easily cover the requirement for any SPWeb, provided that we can recursively loop over all of them (and we can, of course).

3. Recursively looping through all the webs

Once you assign the permission for a web, all the subweb are covered, unless they have unique permission. In this case, you have to individually take care of any subwebs that have unique permission (i.e. they do not inherit the permission from the parent because the inheritance has been broken by an administrator at some point in time):

# Grants read permission to a user for a Web and, recursively,
# all its sub-webs
function Grant-ReadPermRecursive
{
    if($args.Length -lt 2)
    {
        Write-Error "Parameters required: Web Url, Group Name"
    }
    # If runDry = True, the function walks the entire web tree, without actually adding new permissions
    $runDry = $false
    if($args.Length -ge 3)
    {
        $runDry = $args[2]
    }
    Write-Host "RunDry = "$runDry
    $web = Get-SPWeb -Identity $args[0]
    Write-Host "Web: " $web
    $groupName = $args[1]
    Write-Host "Username list: " $groupName
    if(!$runDry)
    {
        # Write-Host "I would actually grant permissions"
        Grant-ReadPermToGroup $web.Url $groupName
    }

    $subWebList = $web.Webs
    if($subWebList.Length -gt 0)
    {
        foreach($subWeb in $subWebList)
        {
            if($subWeb.HasUniquePerm)
            {
                Grant-ReadPermRecursive $subWeb.Url $groupName $runDry
            }
        }
    }
}

4. Wrapping it all

Finally, we can wrap the operation with a convenient function that operates on the farm level, looping over all the SPWebApplications and taking into account the root web of each:

$allFarmWebApplications = Get-SPWebApplication
foreach($webApplication in $allFarmWebApplications)
{
    Write-Host "WebApplication: " $webApplication.Url
    foreach($site in $webApplication.Sites)
    {
        Write-Host "   Site: "$site.Url "   RootWeb: "$site.RootWeb
        $rootWeb = $site.RootWeb
        Grant-ReadPermRecursive $rootWeb.Url "GlobalReaders" $false
        Write-Host "-----------------------------"
        Write-Host " "
    }
}

WPF WebBrowser control – part 2

Registering .NET handlers to DOM elements’ events

With an approach similar to the previous one (see part 1), we can register callbacks that get executed when a DOM event is triggered. Simply put: we can execute .NET code in response, for example, to a click on a HTML input text element. To do this you need to add the MSHTML library to the project references, like I showed in part 1.

As before, let’s assume that we have a simple UserControl that uses a WebBrowser:


<UserControl x:Class="WebBrowserExample.WebBrowserAdapter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <WebBrowser x:Name="WebBrowserControl"></WebBrowser>
    </Grid>
</UserControl>

and we add our UserControl to the main window, just like this:


<Window x:Class="WebBrowserExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:cont="clr-namespace:WebBrowserExample">
    <Grid>
        <cont:WebBrowserAdapter></cont:WebBrowserAdapter>
    </Grid>
</Window>

We can now use types and classes from mshtlm to directly access the DOM of a HTML page. The following snippet reports the complete code of the WebBrowserAdapter class:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using mshtml;

namespace WebBrowserExample
{
    /// <summary>
    /// Interaction logic for WebBrowserAdapter.xaml
    /// </summary>
    public partial class WebBrowserAdapter : UserControl
    {
        public WebBrowserAdapter()
        {
            InitializeComponent();
            this.Loaded += WebBrowserAdapter_Loaded;
        }

        void WebBrowserAdapter_Loaded(object sender, RoutedEventArgs e)
        {
            WebBrowserControl.LoadCompleted += WebBrowserControl_LoadCompleted;
            WebBrowserControl.Navigate("http://www.google.com");
        }

        void WebBrowserControl_LoadCompleted(object sender, NavigationEventArgs e)
        {
            HookHTMLElements();
        }

        private void HookHTMLElements()
        {
            var document = WebBrowserControl.Document as HTMLDocument;
            var inputElements = document.getElementsByTagName("input");
            foreach (var item in inputElements)
            {
                DispHTMLInputElement inputElement = item as DispHTMLInputElement;
                if (inputElement.type == "text" 
                    || inputElement.type == "password" 
                    || inputElement.type == "search")
                {
                    HTMLButtonElementEvents_Event htmlButtonEvent = inputElement as HTMLButtonElementEvents_Event;
                    htmlButtonEvent.onclick += FocusCallback;
                }
            }
        }

        public bool FocusCallback()
        {
            Console.WriteLine("Callback method executed!");
            return true;
        }
    }
}


In the constructor, WebBrowserAdapter registers itself as a listener to its Loaded event. This event is fired when the user control is rendered and ready for interaction. In the WebBrowserAdapter_Loaded it begins to prepare the WebBrowser control: first, it subscribes to the LoadCompleted event of the WebBrowser: the WebBrowser fires this event whenever a navigation successfully ends. Then it makes the WebBrowser navigate to the Google home page, calling the Navigate method.

When the WebBrowserControl_LoadCompleted method gets executed, we can be sure that the WebBrowser holds a valid HTML document and we can safely call its Document property and manipulate the object return. This is what we do in the HookHTMLElements method: here we go retrieve all the elements that have tag name equal to “input”, much like we would do in a Javascript function (in fact, if you take a look at the methods the intellisense proposes you, you will see that they match the Javascript APIs), and we attach .NET callbacks to the DOM events. Now, if you click on the input text box of the Google home page, the FocusCallback method is called and a line is written on the standard output (see pictures below).

callback02

 

 

WPF WebBrowser control – part 1

Introduction

Recently I spent a lot of time developing a .NET WPF application that embeds a web browser (don’t hold me guilty for that, it’s the client’s requirement). Since the standard WPF WebBrowser has a lot of limitations and problems, I had to find several solutions that are not perfectly standard or ideal for the WPF world. This series of posts presents a summary of those solutions that I find particularly interesting. The majority of the material presented here has been taught to me by other developers or gathered in the Internet, though some solutions are invented by me.

Limitations

I found 2 main limitations of the WPF WebBrowser control:

  1. It is not well suited for the MVVM pattern. None of its functionalities is accessible through Dependency Properties, so you have to wrap it somehow if you want to employ it in a MVVM based design. In my case, a decent part of the work has been building an adapter in order to use the WebBrowser in conjunction with the Caliburn.Micro framework.
  2. The set of properties and methods exposedbytheWPF control is extremely limited. When you are using theWebBrowser control you are actually using aWPF wrapper of Internet Explorer. However, it hides a lot of functionalities that the actual underlying implementation has. If you want to have the full (?) power of IE at your disposal, you have to work your way around to get to the underlying COM object. The capabilities that are not visible via the .NET interface includes, but are not limited to, the following:
    1. Use a different IE version (rather than the default IE7);
    2. Prevent the browser from showing annoying dialog windows when a Javascript error occurs;
    3. Attacching .NET handlers to Javascript events;
    4. Injecting HTML and Javascript from .NET code;
    5. Calling Javascript function from .NET code;

Using the Internet Explorer 11 engine and not the IE 7 one

One of the easiest tricks to perform is also one of the subtlest. I don’t remember where I learned it but I definitely did not find it myself. To make sure that the actual Internet Explorer component used inside your WPF application is the one from the version 11 (or possibly higher, in the near future), you have to add a couple of keys to the Windows Registry. Open the editor (run regedit) and go to the branch with path: HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION (Create the folder if it does not exist). Add a new DWORD value for each executable where the WPF WebBrowser control must use IE11, just like this: reg-ie11

The key of the entry must be equal to the name of the executable file of your interest, while the value encodes the version of IE that the corresponding app will use (the hexadecimal value 0x00002af9 for version 11, in this case). Note that some applications may already be there (I had the Acrobat Reader executable that I left there as an example). Also note that if you are developing the application with Visual Studio you may also want to add the name of the executable launched inside the IDE when debugging (the entry with the .vhost.exe suffix ).

How to inject Javascript into HTML documents

First of all, you are going to need some extra dependencies so better add them immediately. Look at the picture below: you must add the mshtml library. You should find it in the Assemblies -> Extensions section of the Reference Manager dialog window.

ref-01

Now let’s suppose that you have a class (tipically a Window or a UserControl) that uses a WebBrowser. You should have something that looks like this: (The XAML)


<UserControl x:Class="ExampleWebBrowserWrapper"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
 mc:Ignorable="d" 
 d:DesignHeight="300" d:DesignWidth="300">

    <Grid>
        <WebBrowser x:Name="webBrowserControl"></WebBrowser>
    </Grid>

</UserControl>     
        

In order to inject a Javascript script into the HTML page, so that it is interpreted and possibly executed in the context of the page, you can implement a method like the following in the ExampleWebBrowserWrapper class (the rest of the class code is ignored for brevity’s sake):


using mshtml;
/* other "using" are omitted */

public partial class ExampleWebBrowserWrapper : UserControl
 {

 public void InjectScript(String scriptText)
 {
 HTMLDocument htmlDocument = (HTMLDocument)webBrowserControl.Document;

 var headElements = htmlDocument.getElementsByTagName("head");
 if (headElements.length == 0)
 {
 throw new IndexOutOfRangeException("No element with tag 'head' has been found in the document");
 }
 var headElement = headElements.item(0);

 IHTMLScriptElement script = (IHTMLScriptElement)htmlDocument.createElement("script");
 script.text = scriptText;
 headElement.AppendChild(script);
 }

}


Then you can call Javascript function (the ones you injected or the ones already contained in the HTML page) from .NET code by calling the InvokeScript method of the WebBrowser class.

        public void InvokeScript(String javascriptFunctionName)
        {
            try
            {
                webBrowserControl.InvokeScript(javascriptFunctionName);
            }
            catch (System.Exception ex)
            {
                /* Handle Exception */;
            }
        }