Friday 22 January 2010

Buying a laptop? Make sure it supports VT

Just borrowed a colleague’s Toshiba M300/Intel Core2 Duo Mobile Processor P7350 laptop only to find the CPU doesn’t support VT (or the Intel Virtualization Technology/hardware-assisted virtualisation) required by Hyper-V and VMware; I was planning to use this laptop for Hyper-V-based demos. Rats.

The laptop in question is only six months old and otherwise nicely equipped with 4GB of RAM and a 320GB hard drive so it’s a shame about this niggle… back to Virtual PC and 32-bit for me I suppose—how quaint!

Which leads me this mental note: when buying a laptop go to extreme lengths to determine whether the CPU offers VT and do some Googling to determine whether it can actually be enabled in the BIOS (apparently the various manufacturers can disable/enable VT at their discretion).

In the P7350 case, Intel’s technical data was incorrect on publication—originally specifying support for VT. If you’ve actually got one of these and are reading this post, sorry mate—hope you weren’t planning to do too much 64-bit virtualisation!

Wednesday 20 January 2010

SharePoint 2007 Slipstreamed Installer

I hadn’t noticed previously but Microsoft released slipstreamed SP2 installers (x86 and x64) at the end of 2009:

http://www.microsoft.com/downloads/details.aspx?FamilyId=3015FDE4-85F6-4CBC-812D-55701FBFB563&displaylang=en

http://www.microsoft.com/downloads/details.aspx?familyid=2e6e5a9c-ebf6-4f7f-8467-f4de6bd6b831&displaylang=en

Makes life a little bit easier so you don’t have to do the slipstreaming yourself. Apparently previous installers will not work with Windows Server 2008 R2.

Saturday 16 January 2010

Manage Active Directory Users and Groups with PowerShell

With a new dawn and the SharePoint 2010 beta upon us, I figured it was finally time to a) dip my toes into the PowerShell pool and b) learn how to automate the creation of users, groups, organisational units, and other objects within Active Directory. In the PS case, it still seems like an unnecessary evil and, between all the other things on my “TO LEARN” list, it hasn’t been a high priority; that said, it seems to work and I kind of, sort of, maybe just like it. We’ll see… On the automation front, I figured I’ll probably blow away more than my fair share of development environments before my time is up so I might as well make creating the darned things a little bit easier and more repeatable.

My search for information along these lines quickly led me to Quest’s ActiveRoles Management Shell for Active Directory—a set of PowerShell commandlets. The download is free and if you grab it I’d also strongly suggest you pull down the admin guide as well—it’s comprehensive and includes examples. To install, run the .msi and finish the wizard with the defaults.

From there you’ll need to add the related snap-in in a PS window as follows:

Add-PSSnapin Quest.ActiveRoles.ADManagement

The first thing I wanted to create was a user object and this was reasonably straightforward. Be warned some of the commandlets take a lot of arguments, the names of which don’t always correspond to the UI you see in the AD Users and Computers snap-in: password is –UserPassword, for example, and Job Title is simply –Title. Here’s the command I used:

new-QADUser -name 'Bob' -ParentContainer 'OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com' -SamAccountName 'Bob' -UserPassword 'TH1Smis1s' -FirstName 'Bob' -DisplayName 'Bob' -UserPrincipalName 'bob@spsdev.mediawhole.com' -Title CEO

This creates the user but new-QADUser doesn’t allow you set all of the things I want to using the standard parameters. In my case, I also want to set the account to never expire. To do this, I used the set-QADUser commandlet as well, which seems to let you get to much more detail:

set-QADUser 'CN=Bob,OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com' -PasswordNeverExpires $true

Next I created a new group using the new-QADGroup commandlet:

new-QADGroup -ParentContainer 'OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com' -name 'Managers' -samAccountName 'Managers' -grouptype 'Security' -groupscope 'Global'

…before adding my new user to my new group with the add-QADGroupMember commandlet:

add-QADGroupMember -identity 'CN=Managers,OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com' -member 'CN=Bob,OU=DirtyWords,DC=spdev,DC=mediawhole,DC=com'

This last exercise proved somewhat troublesome as the examples in the admin guide and specific documentation about the –member parameter indicated I could supply ‘spsdev\Bob’ in place of the string above I ended up using successfully. The error message was fairly explicit about this:

Add-QADGroupMember : Cannot resolve directory object for the given identity: 'spsdev.mediawhole.com\bob'.

One other commandlet I haven’t yet managed to get working is the new-QADObject commandlet to create a new OU:

new-QADObject -ParentContainer 'DC=spsdev,DC=mediawhole,DC=com' -type 'organizationalUnit' -NamingProperty 'ou' -name 'Dirty Words'

This command fails on the type parameter with;

New-QADObject : A referral was returned from the server…

+ CategoryInfo          : NotSpecified: (:) [New-QADObject], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException,Quest.ActiveRoles.ArsPowerShellSnapIn.Powers
hell.Cmdlets.NewObjectCmdlet

I’ll probably look back at this post one day after figuring this one out and laugh at my lack of understanding but for now it’s got me stumped.

 

Tuesday 12 January 2010

Can’t start Hyper-V VM after Windows Server 2008 R2 upgrade

Oh the tedium… Having used Windows Server 2008 R2 for a while, I was keen to upgrade the Hyper-V host environment supporting my dev infrastructure from RTM to R2. R2 seems faster and more refined; I was also wanting to play with the latest version of the System Center Virtual Machine Manager—which requires R2.

The upgrade process went smoothly once I figured out the installer would eventually present me with the upgrade option after telling it to install the Windows Server 2008 R2 Standard (Full Installation) option; this was briefly confusing to me  since I didn’t want to do a full installation! I shut down all my VMs properly beforehand and everything seemed pretty much good (I did have to recreate the one VM I normally auto-start but luckily I was able to reuse the .vhd itself and the machine came back to life with little hassle).

Today, however, I went to create a new VM but was unable to start it post-creation. Clicking start popped up a message telling me the VM could not initialize and Hyper-V could not create or access saved state file. The physical location of my VMs hadn’t changed but I read this error message as a permissions issue—but where to start?! The only thing I had to go on was that I was creating the new VM on another volume.

Fortunately Jevgenij Martynenko saved the day for myself and others in this forum post with the old trick of granting access to the root of the drive. I remember having to do this for ASP.NET back in the day for some reason…

Anyway, as per the instructions, I granted ‘Authenticated Users’ ‘List folder / read data’ permission to the root of my E:\ drive, setting the scope drop down to This folder only. I did not replace all child object permissions as apparently that causes more problems.

Starting the new VM was successful after this change.

Windows Server 2008 R2 Scratch Disk

When building out a new VM I’ve occasionally created a second disk for holding non-system files and temporary data; this disk is easy to blow away every so often and saves the hassles of defragging and compressing.

I recently built a Windows Server 2008 R2 VM and created a system drive and secondary drive in Hyper-V manager before installing the OS. During the OS installation process, I created a new partition and noticed—but thought nothing of—the fact the installer didn’t nag me about creating a reserved system partition as well.

Carrying on my merry way, I eventually found the secret system reserved partition on my “scratch” disk (d:\) when I went to format what I expected was a completely unallocated disk. Bummer—I’m not clear on the ramifications of simply deleting this reserved partition but it currently means dragging around an additional 6GB of D: drive and being unable to blow away my scratch disk.

Saturday 9 January 2010

I’m Speaking at SharePoint Saturday Perth this February

How exciting—our very first SharePoint Saturday event in Perth! A BIG shout out to Jeremy Thake for organising the venue, speakers, sponsors, prizes, and everything else! The speaker line-up looks fantastic and the sessions cover a range of topics so I reckon this is going to be a great day.

In addition to attending the event, I’ll also be presenting on the subject of SharePoint list development. Here’s the official blurb for my talk:

Lists are key to understanding the power of the SharePoint platform and provide core data storage and management facilities in a SharePoint environment. Lists can be customised to suit custom requirements but doing so isn’t always straightforward.

Learn how to create, deploy, manage, and interact with SharePoint lists in this jam-packed session. Discussion will cover the pros and cons of using SharePoint lists before focusing on methods for creating and deploying lists between environments in a repeatable manner; techniques for working with lists and data using the SharePoint API with also be demonstrated.

Existing experience with the SharePoint UI, a cursory knowledge of content types, and ASP.NET development experience is recommended but not assumed.

If there’s anything you’d like me to cover in particular please drop me a line or post a comment below. If all goes well I’ll also be recording the session so feel free to make a request or post a question even if you’re unable to attend in person.

Finally, Diversus are sponsoring the event and their package includes a JVC video camera to be given away—thanks guys!!!

Diversus - Perth SharePoint Development

Friday 8 January 2010

Add and manage workflows using the API

Programmatically applying and configuring an out-of-the-box workflow to a SharePoint list isn’t the most obvious thing to do but fortunately it’s not exactly hard either. I had to dig around a bit to find the pieces I need to do this and thought I’d share…

List Settings - Versioning

In my case I’m applying an approval workflow to a Pages library created by the Publishing feature; before I wrangle the workflow I need to configure the versioning settings on my Pages list. Through the UI, I can do this by configuring these settings as follows:Configure_List_Versioning_for_WorkflowEssentially, content approval is required, users need to be able to create major and minor (draft) versions, all editors should be able to see all drafts, and checkout is required to edit documents.

To do this setup programmatically, I set the following properties on my Pages list:

SPList pages = web.Lists ["Pages"];

// Require content approval
pages.EnableModeration = true;
// Create major and minor versions
pages.EnableVersioning = true;
pages.EnableMinorVersions = true;
// Who should see draft items
pages.DraftVersionVisibility = DraftVisibilityType.Author;
// Require documents to be checked out before they can be edited
pages.ForceCheckout = true;

pages.Update ();

Remove Existing Workflows

In some cases, you may also want to cleanup a list’s existing workflows (i.e. remove them) and this is easily accomplished using the SPList.RemoveWorkflowAssociation() method. Note several examples on the interwebs suggest doing this within a foreach loop but doing so will invalidate the enumerator after the first item is removed; use a for loop or a while loop instead:

// Remove existing workflows
for (int associationIndex = 0; associationIndex < pages.WorkflowAssociations.Count; associationIndex++)
{
    SPWorkflowAssociation workflowAssociation = pages.WorkflowAssociations [associationIndex];
    pages.RemoveWorkflowAssociation (workflowAssociation);
}

If you need to remove individual workflows, inspect the SPWorkflowAssociation.Name property.

Workflow Setup…

With that out of the way, let’s move on to the workflow configuration proper. In short, it’s all about the collection of SPWorkflowAssociation objects exposed through the WorkflowAssociations property of the SPList class. The AssociationData property exposed by the SPWorkflowAssociation class is also crucial.

Task and History List Management

When you setup a workflow through the UI, you’ll be prompted to specify names for the task and history lists (the latter of which will be hidden); when you’re doing this programmatically, you have to manage this part yourself. It would have been nice if the API did this automatically but it doesn’t. The CreateListAssociation method we’ll examine in a moment also requires a reference to each list so you need to a) determine if each list exists or create it and b) retrieve a reference to each list. To wrap all of this logic up together, here’s my helper method (based on this by Marco):

private void EnsureWorkflowLists (SPWeb web, out SPList taskList, out SPList historyList)
{
    const string WORKFLOW_TASK_LIST_NAME = "Workflow Tasks";
    const string WORKFLOW_HISTORY_LIST_NAME = "Workflow History";

    // Task list

    try
    {
        taskList = web.Lists [WORKFLOW_TASK_LIST_NAME];

        Console.WriteLine ("Tasks list found.");
    }
    catch (System.ArgumentException)
    {
        // List does not exist so create it
        Guid taskListId = web.Lists.Add (WORKFLOW_TASK_LIST_NAME, string.Empty, SPListTemplateType.Tasks);
        taskList = web.Lists [taskListId];

        Console.WriteLine ("Created task list for web.");
    }

    // History List

    try
    {
        historyList = web.Lists [WORKFLOW_HISTORY_LIST_NAME];
        Console.WriteLine ("History list found.");
    }
    catch (ArgumentException)
    {
        Guid historyListId = web.Lists.Add (WORKFLOW_HISTORY_LIST_NAME, string.Empty, SPListTemplateType.WorkflowHistory);
        historyList = web.Lists [historyListId];
        historyList.Hidden = true;
        historyList.Update ();

        Console.WriteLine ("Created history list for web.");
    }                        
}

Call EnsureWorkflowLists method like so:

SPList taskList;
SPList historyList;
EnsureWorkflowLists (web, out taskList, out historyList);

Workflow Template

Before you can finally create the new workflow association, you’ll also need an SPWorkflowTemplate. You can do this by calling the GetTemplateByName method hanging indirectly off the SPWeb object:

SPWorkflowTemplate approvalTemplate = web.WorkflowTemplates.GetTemplateByName ("Approval", System.Globalization.CultureInfo.CurrentCulture);

If you have any issues here, ensure the Routing Workflows feature (or the relevant feature for whatever OOB workflow you’re dealing with) is enabled at the site collection level (/_layouts/ManageFeatures.aspx?Scope=Site).

Create the Workflow Association

With the leg work behind us, actually applying a new workflow association to the list and configuring it is quite easy, with one exception I’ll cover in a moment. To begin, create a new workflow association using the SPWorkflowAssociation.CreateListAssociation method and supply the workflow template and list references created previously:

SPWorkflowAssociation approvalWorkFlowAssociation = SPWorkflowAssociation.CreateListAssociation (approvalTemplate, "Page Approval", taskList, historyList);

Note the name parameter “Page Approval” is arbitrary but will appear in the UI and notification emails; I therefore aim to make it user friendly and somewhat specific.

The created object can then be configured, in particular using the AllowManual, AutoStartChange, and AutoStartCreate properties.

As mentioned, there’s one last hurdle and that’s providing the XML string to the SPWorkflowAssociation.AssociationData property. As far as I’m aware, this is the only way to specify the nominated approvers group and a few other properties such as the ability to delegate tasks and whether a change to the list item should interrupt the current workflow. I believe the easiest way to create this string is to copy an example (below) or configure the workflow as desired in the UI and use SharePoint Manager to extract the XML from the workflow association (highlighted in the image below).

WorkflowAssociations_AssociationData_SPM

<my:myFields xml:lang="en-us" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD">
<my:Reviewers>
<my:Person>
<my:DisplayName>Approvers</my:DisplayName>
<my:AccountId>Approvers</my:AccountId>
<my:AccountType>SharePointGroup</my:AccountType>
</my:Person>
</my:Reviewers>
<my:CC></my:CC>
<my:DueDate xsi:nil="true"></my:DueDate>
<my:Description></my:Description>
<my:Title></my:Title>
<my:DefaultTaskType>1</my:DefaultTaskType>
<my:CreateTasksInSerial>false</my:CreateTasksInSerial>
<my:AllowDelegation>true</my:AllowDelegation>
<my:AllowChangeRequests>true</my:AllowChangeRequests>
<my:StopOnAnyReject>true</my:StopOnAnyReject>
<my:WantedTasks xsi:nil="true"></my:WantedTasks>
<my:SetMetadataOnSuccess>false</my:SetMetadataOnSuccess>
<my:MetadataSuccessField></my:MetadataSuccessField>
<my:MetadataSuccessValue></my:MetadataSuccessValue>
<my:ApproveWhenComplete>true</my:ApproveWhenComplete>
<my:TimePerTaskVal xsi:nil="true"></my:TimePerTaskVal>
<my:TimePerTaskType xsi:nil="true"></my:TimePerTaskType>
<my:Voting>false</my:Voting>
<my:MetadataTriggerField></my:MetadataTriggerField>
<my:MetadataTriggerValue></my:MetadataTriggerValue>
<my:InitLock>true</my:InitLock>
<my:MetadataStop>false</my:MetadataStop>
<my:ItemChangeStop>true</my:ItemChangeStop>
<my:GroupTasks>true</my:GroupTasks>
</my:myFields>

Drop the XML into the SPWorkflowAssociation object you just created using the AssociationData property and you’re all set to add the association to your list using the SPList.AddWorkflowAssociation method:

pages.AddWorkflowAssociation (approvalWorkFlowAssociation);

Before updating the list list, you may also want to configure your new workflow as the default content approval workflow:

pages.DefaultContentApprovalWorkflowId = approvalWorkFlowAssociation.Id;

Follow that with pages.Update () and you’re done!

Monday 4 January 2010

Enable ICMP echo (ping) in Windows Server 2008 R2

Windows Server 2008 won’t respond to ping requests out of the box—they’re blocked by default in the Windows Firewall ruleset. In the pre-R2 days, I used a simple command to enable ping in my development environments but apparently netsh firewall has been deprecated:

netsh firewall set icmpsetting 8

The replacement is—wait for it—netsh advfirewall firewall, like so:

netsh advfirewall firewall add rule name=”ICMP Allow incoming V4 echo request” protocol=icmpv4:8,any dir=in action=allow

Richard Siddaway has a PowerShell equivalent (presumably, I haven’t tried it myself but should have, I know… tsk, tsk).