Wednesday, December 15, 2010

Powershell and ONET.XML gotchya

I have been suspecting this for some time now, but as of today I am sure that there is some funny caching going on in the PowerShell New-SPSite cmdlet.

I was working on a custom site definition for SharePoint 2010, specifically I was messing around in the ONET.XML with web part placement. I had changed the layout of a page and needed to move my web parts to different zones. So I changed the WebPartZoneID attribute as usual, but I could not get it to work. My web parts were being inserted into the same old zone as before. I gave up eventually and thought I'd solve it another day.

Today after a fresh boot, I redeployed my site again and all was working as expected. Huh?

The way I was working the other day was that I had a PowerShell command line session open and I was simply issuing two commands to delete and create a new site based on my definition. Sometimes I would use the quick deploy method from CKSDEV and sometimes I would do a full deployement between these commands:

Remove-SPSite http://myintranet

New-SPSite http://myintranet -OwnerAlias XCOMPLICA\Joe -Template XCOMPLICA#0 
Regardless of what I did however, the New-SPSite command never picked up any changes I made to the ONET.XML file. I have now confirmed with a few more tests that I need to start up a new Powershell console for this to happen. I even tried iisreset and resetting all the app pools of SharePoint - no difference. It's cached somewhere in the cmdlet.

I guess I am putting my commands in a script I can call from a batch file, or maybe I'll try doing something with Powershell scopes to resolve this.

In any case beware of this caching, it cost me half a day.

Tuesday, December 07, 2010

Nicer Claims Login Page SharePoint 2010

I have been working on a Forms Based Authentication (FBA) solution for an internet site and one of the requirements was the creation of a custom login page, one that will be somewhat more user friendly than the drop down page that SharePoint 2010 uses.

Most internet users will have no clue what to do with the "choose your claims provider" drop down so we wanted to show a page with a username and password box where most users will enter their credentials. These will be authenticated against the SQL database backed FBA, exactly what needs to happen for internet users. However we also want to show a small link that will automatically log in our internal users with Windows authentication.



This is not too difficult but I can see myself needing this again thus the blog post.

I started with following this blog post on Creating a Custom Login Page for SharePoint 2010 by Kirk Evans, which worked great. Following his instructions gets you to the point of having a login page for your FBA users. All you need now is that link for your internal users.

First I modified the login control to make sure it goes against the correct Membership Provider, by adding the MembershipProvider attribute like:

<asp:Login ID="signInControl" FailureText="<%$Resources:wss,login_pageFailureText%>" MembershipProvider="FBAMembershipProvider"
        runat="server" Width="100%" DisplayRememberMe="false" />

To be honest I'm not sure if that is needed, it just seemed logical to me.

Next I added a LinkButton right under the login control so I had markup like:

<asp:Login ID="signInControl" FailureText="<%$Resources:wss,login_pageFailureText%>" MembershipProvider="FBAMembershipProvider"
        runat="server" Width="100%" DisplayRememberMe="false" />
<asp:LinkButton ID="hlInternalUsers" Text="Internal Login" runat="server" />

Lastly I added some simple code to the code beside file: (explanation below)

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            hlInternalUsers.Click += new EventHandler(hlInternalUsers_Click);
        }

        void hlInternalUsers_Click(object sender, EventArgs e)
        {
            if (null != SPContext.Current && null != SPContext.Current.Site)
            {
                SPIisSettings iisSettings = SPContext.Current.Site.WebApplication.IisSettings[SPUrlZone.Default];
                if (null != iisSettings && iisSettings.UseWindowsClaimsAuthenticationProvider)
                {
                    SPAuthenticationProvider provider = iisSettings.WindowsClaimsAuthenticationProvider;
                    RedirectToLoginPage(provider);
                }
            }
        }

        //borrowed from Microsoft.SharePoint.IdentityModel.LogonSelector
        private void RedirectToLoginPage(SPAuthenticationProvider provider)
        {
            string components = HttpContext.Current.Request.Url.GetComponents(UriComponents.Query, UriFormat.SafeUnescaped);
            string url = provider.AuthenticationRedirectionUrl.ToString();
            if (provider is SPWindowsAuthenticationProvider)
            {
                components = EnsureUrlSkipsFormsAuthModuleRedirection(components, true);
            }
            SPUtility.Redirect(url, SPRedirectFlags.Default, this.Context, components);
        }

        //borrowed from Microsoft.SharePoint.Utilities.SPUtility
        private string EnsureUrlSkipsFormsAuthModuleRedirection(string url, bool urlIsQueryStringOnly)
        {
            if (!url.Contains("ReturnUrl="))
            {
                if (urlIsQueryStringOnly)
                {
                    url = url + (string.IsNullOrEmpty(url) ? "" : "&");
                }
                else
                {
                    url = url + ((url.IndexOf('?') == -1) ? "?" : "&");
                }
                url = url + "ReturnUrl=";
            }
            return url;
        }


The link button has an event handler attached to its Click event. The event handler makes sure there is an SPContext to work with and then gets an SPIisSettings object for the Default Zone of the current site. You can change the zone here if needed. If the SPIisSettings object is succesfully retrieved, we can check if Windows Authenticatoin is being used by this zone. If not, our link makes no sense! Next we get the Windows Authentication provider and call a helper method that will redirect the user to the right place.

The RedirectToLoginPage method is actually borrowed from the Microsoft.SharePoint.IdentityModel.LogonSelector class that is the control responsible for the drop down on the Out-of-box Claims logon page. As far as I can tell it plays with the urls so that the user is redirected to the correct login page (depending on the provider). Notice that this method uses yet another helper method, the EnsureUrlSkipsFormsAuthModuleRedirection method. This is a method borrowed from the SPUtility class, sadly it is internal so I just copied it out of reflector. You can use reflection if you want but I didn't want the performance hit. It just helps with some url magic.

So with a few lines of our own code and a few borrowed thanks to reflector, we have a login page that is very simple for our internet FBA users but still allows our internal users to log in with Windows Auth by clicking a simple link.

Friday, December 03, 2010

Beware Parallels 6

I have been using Parallels virtualization for almost two years now, and I was completely happy with version 4 and 5.  VMs were stable and fast, no issues. Recently version 6 came out, and it has been a total piece of sh*t.  It has managed to destroy a number of VMs at this point from corrupting SharePoint dlls to making windows think it is no longer a genuine copy. I thought maybe I'd just create a clean install of W2K8 based on version 6 since sometimes VMs that are upgraded can have issues right?  Well I can't even install a fresh copy of W2K8 x64 with this version of Parallels! I get this lovely screen:


Completely useless to me now. I'm soooo glad I spent the €49.95 on it. If you have Parallels 5, DO NOT UPGRADE!