I'm borrowing a commonly recurring phrase from Waldek Mastykarz's blog with my title, because the only other thing I could come up with involved expletives. Lots of them.

If you've had to deal with SharePoint sites with anonymous access programmatically, you may have come across AnonymousPermMask64 which lets you specify what levels of access anonymous users have, if any. If your code just sets this value and moves on, it's not a big deal. But it's a tricky bugger in other cases:

  1. Once you assign a value, attempting to read the property back will cause an InvalidCast exception to be thrown (in PowerShell this is silently trapped, so you just get back null)
  2. Trying to assign another value is just as fruitless, again throwing InvalidCast in both PowerShell and .NET (e.g. maybe you're building up the permission mask with bitwise operations in a loop, or conditionally assigning permissions based on some settings)
  3. The changes are applied immediately. You don't need to do an Update() on either the SPList or SPWeb, which probably isn't what you'd want if you trap an exception later on and have to rollback, or if running some script in a test/dry-run mode.

I came across this weirdness when I was using PowerShell to automate (amongst many other things) some permission changes, and was going through the usual dance of 'if I run this command, does it actually do what I expected?' So naturally, when I assigned some permissions to a list via $list.AnonymousPermMask64 = $somePermission, and immediately wrote the property back to screen to see nothing, I got a little confused.

After much hair pulling and wondering if I'm just slowly becoming stupid (have I forgotten how to assign a value to a property‽), I came to the conclusion that something is stupid, but it's not totally me!

Here's a quick script I wrote to double-check my own sanity:

# Load the web/list, and see what the permmask is currently set to
PS> $web = get-spweb $webUrl
PS> $list=$web.Lists["Documents"]
PS> Write-Host $list.AnonymousPermMask64

# OK, nothing set. Good, let's set it to OpenItems,
# then make sure it actually took effect…
PS> $list.AnonymousPermMask64 = [Microsoft.SharePoint.SPBasePermissions]::OpenItems
PS> Write-Host $list.AnonymousPermMask64
(no output)
PS> $list.AnonymousPermMask64 -eq $null

# That… sucks. Maybe instantiate the web/list and try again?
PS> $web = get-spweb $webUrl
PS> $list=$web.Lists["Documents"]
PS> Write-Host $list.AnonymousPermMask64

# What.. the… hell. It's actually set to OpenItems??

The scariest things here are:

  1. The output is null after assigning a value
  2. Without actually doing an Update() anywhere, the new value has in fact taken hold

So at this point, a sane person might just die a little inside, accept this as another SharePoint quirk, and move on. But not me, I want to know what the hell is causing this. Reflection time.

Here's an abbreviated implementation of AnonymousPermMask64:

public SPBasePermissions AnonymousPermMask64
        return (SPBasePermissions)((long) this.m_arrListProps[21, this.m_iRow]);
        SPWeb parentWeb = this.ParentWeb;
        parentWeb.Request.SetAnonymousAccessMask (parentWeb.Url, this.InternalName, (ulong)value);
        this.m_arrListProps[21, this.m_iRow] = value;

The thing that jumps out at me was all the casting that's going on in the getter, but not much in the setter (especially given the aforementioned InvalidCast exceptions).

But why does the getter work the first time? Well, the m_arrListProps is a 2D array of object. When the properties are first pulled from the database, the value for AnonymousPermMask64 is cast as an int. And all the casts are happy. But when you call the setter, it becomes an SPBasePermissions, and all of a sudden the getter can't cast to a long (only to immediately cast back to SPBasePermissions. Sigh).

Honestly, I can't say exactly why it's failing (which is really unsatisfying). While an implicit conversion exists from uint to long, I guess .NET can't implicitly convert a SPBasePermissions enum to uint then to long, so throws a cast error.

I wrote another script using reflection magic to poke around directly in the bowels of SPList.m_arrListProps. Behold!


What I found was that if the internal value was assigned as a long rather than SPBasePermissions, everything worked nicely. Surprise, surprise.