Sunday, February 20, 2011

Create custom AD attribute and map it to Sharepoint user profile property

In this post I would like to show the guide of how you can expand your AD schema with custom attributes and then map them on User Profile properties in Sharepoint. Although there is a lot of standard attributes which can be used for storing information in AD, this is common situation when they don’t match requirements of enterprise infrastructure. Imagine situation when company has various business units which should be stored in AD with other information about users and we need to make them searchable in the Sharepoint intranet (in this article by Sharepoint I mean Sharepoint 2010). Once user profiles have indexed business unit property Sharepoint will crawl them OTB and show in the people search result. So our goal is to map AD attribute, which is used for storing business units, on User Profile property.

There is no attribute “Business Unit” in standard AD schema. Of course there is a lot of other attributes and you can use them for storing business units information. The only thing you need to do is to find some suitable attribute (which name is more or less close to the real attribute purpose and which has appropriate data type), e.g. if we assume that business units are text data we can use standard Business-Category (businessCategory) attribute (Type = Unicode String). But this approach has own disadvantages: we introduce one more implicit solution into IT infrastructure of the company (how many such solutions already exist in your IT infrastructure? :) ), if in the future we will need to use business category attribute for its real purposes we will need to perform additional work: select another standard attribute (and we are also not saved from the same problems in the future with the new attribute), migrate existing data, reconfigure Sharepoint, etc.

Another solution is to expand your AD schema by creating new custom attribute and then map this attribute on Sharepoint user profile property. It will require more work from us, but solution will be explicit (data will be stored in the place where it is supposed to be) and we will not problems with attributes reusing in future.

First of all we need to create custom AD attribute. In order to do it I recommend to follow this article: Adding Custom Attributes in Active Directory. We don’t need to perform all steps from it, so I will put exact steps (steps 1-18) here (also I want to collect all steps in one article for future references) and add my comments to the steps:

  1. Ensure that you have enabled AD schema updates (parameter “Schema Update Allowed” = 1 of type REG_DWORD in the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters
  2. Install the Schema snap-in (Start > Run > regsvr32 schmmgmt.dll)
  3. Go to Start > Run > Type MMC and press Enter
  4. Go to File > Add/Remove Snap-in > click Add > Select “Active Directory Schema” (it will occur after step 2 only, i.e. after you installed Schema snap-in) and click Add
  5. Expand the Active Directory schema and Right Click Attributes
  6. Click “Create Attribute”
  7. Create New Attribute window will appear
  8. In Common name enter “Business Unit” (LDAP name as “businessUnit”)
  9. Get OID please refer http://msdn2.microsoft.com/en-us/library/ms677620.aspx. Shortly you need to obtain the basic ID using the script available here. You will get basic object identifier, which will look like sequences of digits separated by dots: “1.2.840.113556.1.6.1.8000.xxxx”. In order to create OID for attribute you can add “.2.1” suffix, so the final ID will be “1.2.840.113556.1.6.1.8000.xxxx.2.1”
  10. Select the Syntax = “Unicode String”
  11. Click Ok. After that new attribute will be created
  12. Now we need to add this attribute to “Person” class
  13. Expand Classes > Select Person
  14. Rick click Person and select Properties
  15. Click Attribute Tab and click Add
  16. Select the Attribute “businessUnit” and click OK (on this step in my environment snap-in shows exception, but after I reopen MMC console it shows that changes were saved successfully)

Now you can specify business units for users in AD. Go to Start > Administrative Tools > Active Directory Users and Computers. Enable advanced features in View > Advance Features. Now if you click on some user account you will find “Attribute Editor” tab. In the attributes list you will find added attribute “businessUnit” (it will occur here after you added attribute to the Person class – see above). In order to specify some value for new attribute select it and click Edit, then enter new value in the Value field.

Ok, we created custom AD attribute. Now we need to map it on User Profile property in Sharepoint User Profile Service application. There is another useful article which you can start with: Mapping User Profile Properties in SharePoint 2010 to LDAP Attributes. Unfortunately for my case it didn’t work like described in the article. The main problem is that new custom attribute was not shown in User Profile properties mappings (Central Administration > Manage service applications > User Profile Service Application > Manage User Properties > New property > Add new mapping section > Attribute list). Mentioned post says that in this case you need to run PowerShell script which will map attribute:

   1: function MapAttribute([string]$url, [string]$fimProperty, [string]$spsProperty, [string]$connectionName)
   2: {
   3:     $site = Get-SPSite $url 
   4:  
   5:     if ($site) 
   6:         {Write-Host "Successfully obtained site reference!"} 
   7:     else 
   8:         {Write-Host "Failed to obtain site reference"} 
   9:  
  10:     $serviceContext = Get-SPServiceContext($site) 
  11:  
  12:     if ($serviceContext) 
  13:     {Write-Host "Successfully obtained service context!"} 
  14:     else 
  15:         {Write-Host "Failed to obtain service context"} 
  16:     $upManager = new-object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($serviceContext) 
  17:  
  18:     if ($upManager) 
  19:         {Write-Host "Successfully obtained user profile manager!"} 
  20:     else 
  21:         {Write-Host "Failed to obtain user profile manager"} 
  22:     $synchConnection = $upManager.ConnectionManager[$connectionName] 
  23:  
  24:     if ($synchConnection) 
  25:         {Write-Host "Successfully obtained synchronization connection!"} 
  26:     else 
  27:         {Write-Host "Failed to obtain user synchronization connection!"} 
  28:  
  29:     Write-Host "Adding the attribute mapping..." 
  30:     $synchConnection.PropertyMapping.AddNewMapping([Microsoft.Office.Server.UserProfiles.ProfileType]::User, $spsProperty, $fimProperty) 
  31:     Write-Host "Done!"
  32: }

If you will put this script into “map.attribute.ps1” file, then you can call it like this:

   1: $thisDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent;
   2: . ($thisDir + '\map.attribute.ps1')
   3:  
   4: $url = "http://example.com" #URL of any site collection that is associated to the user profile service application. 
   5: $spsProperty = "BusinessUnit" #Internal name of the SharePoint user profile property 
   6: $fimProperty = "businessUnit" #Name of the attribute in FIM/LDAP source 
   7: $connectionName = "MyConnection" #Name of the SharePoint synchronization connection
   8:  
   9: MapAttribute $url $fimProperty $spsProperty $connectionName

When I ran this script on my environment, new attribute didn’t appear in the available mapping list in the User Profile properties. However didn’t try to map without it, so I decided to put it here also for reference (may be on your environment it will work). In my case in order to get it work (i.e. in order to display “businessUnit” attribute in mappings list) I needed to recreate User Profile Service Application synchronization connection:

  1. Go to Central Administration > Manage service applications > User Profile Service Application > Configure Synchronization Connection
  2. Delete existing connection (in script above it is called “MyConnection”) and create a new one with the same name
  3. Perform full profile synchronization: Central Administration > Manage service applications > User Profile Service Application > Start Profile Synchronization
  4. After full synchronization will be finished go to User Profile properties. Now you should see “businessUnit” attribute in the attributes list available for mapping. Create new property and map it to the custom AD attribute. Check “Indexed” checkbox – in this case it will be available for Sharepoint crawler.

This is all what I wanted to say about creation of custom AD attribute and its mapping to the User Profile property. Hope this information will help you in your work.

Update 2011-04-09: in the following post I show how you can customize People search and display custom AD attributes on search result page: Add custom AD attributes to People search results in Sharepoint.

Tuesday, February 15, 2011

Programmatically change background color of the web part header

Recently I faced with the following problem: we had requirement for particular custom web part: its header should have appropriate color (which differs from default color of all another web parts headers). During investigation I found that there are not so much solutions, which match all our requirements. It is relatively easy to change header color of all web parts on your site: you just need to play with “ms-WPHeader” style as described here. But it is not so easy to apply change on particular web parts. E.g. solution described here assumes that you should create custom class – inheritor of WebPartZone (ASP.Net WebPartZone, not Sharepoint WebPartZone which is sealed) and custom Chrome type. It is interesting solution, but with it you will need to use custom classes everywhere you want to customize header of the web part. Also it assumes that all web parts from WebPartZone will have the same chrome (which is better comparing with the case of all web parts, but not perfect still).

Another solution shows how to use Content Editor web part in order to add a style which will be uniquely identify header of one particular web part (because of using identifier “MSOZoneCell_WebPartWPQ2” in style) and apply css style to this header. This is also interesting solution – unfortunately it requires manual actions.

In ideal I would want to have control over web part header occurrence inside web part itself. And of course changes made to this web part should not affect all other web parts.

I tried several ways to achieve the result (both server side and client side). E.g. I tired to use Control Adapter for WebPartZone – where you can override Render() method and add additional styles to the header (technically web part header is “tr” element as you will show below). It works but requires modification of .browser files and complicated regex manipulation. Also overridden Render() method when you render control to the string and then replace something in this string can affect performance.

After playing a bit with client side solution (using jquery) I decided to combine server side and client side approaches.

First of all we need to add jquery reference on our master page (into head section):

   1: <SharePoint:ScriptLink ID="ScriptLink4" language="javascript" name="/_layouts/Fiskars/scripts/jquery-1.4.4.min.js" OnDemand="False" LoadAfterUI="False" Localizable="False" runat="server"/>

Then you need to add the following javascript function in the master page also:

   1: <script type="text/javascript">
   2:     function changeTitleColor(id) {
   3:         jQuery('#' + id).parentsUntil("td[id^='MSOZoneCell_']").find('tr.ms-WPHeader').css('background-color', 'Orange');
   4:     }
   5: </script>

In order to understand what it does lets consider what is actually web part in document object model:

image

Generally web part is rendered as a table (class s4-wpTopTable) with 2 rows: first row is for header, 2nd row is for web part content. The problem that inside web part we can control only those part which is rendered in 2nd row (i.e. only web part content – see picture). All another parts like header and border are controlled using web part chrome which is retrieved from WebPartZone. The good news is that inside web part we have access to it ClientID which we can use for accessing mentioned components using jquery (on the picture above it is “ctl00_m_g_72570dc0_fe27_43ca_82b9_8f324b8370eb”). Now lets return to the javascript function “changeTitleColor()” and see what it does. First of all it goes top most to the parent element which has “id” attribute beginning with “MSOZoneCell_” string:

   1: .parentsUntil("td[id^='MSOZoneCell_']")

As shown on the picture it stops on the following element:

   1: <table class="s4-wpTopTable" border="0" cellpadding="0" cellspacing="0" width="100%">

(parentUntil() method doesn’t include the element itself which matches condition). The rest is simple: we find all descendent “tr” elements which have “ms-WPHeader” class and change their css “background-color” property to the “Orange”:

   1: .find('tr.ms-WPHeader').css('background-color', 'Orange');

The only thing we need to do is to call our javascript function with identifier of the web part. It can be done by single line of code which should be added into CreateChildControls() method of your web part:

   1: protected override void CreateChildControls()
   2: {
   3:     ...
   4:     this.Controls.Add(new LiteralControl(string.Format("<script type=\"text/javascript\">changeTitleColor('{0}');</script>", this.ClientID)));
   5:     ...
   6: }

That’s all: after this you will have possibility to specify web part header color in the code of web part itself.

Saturday, February 12, 2011

Converting ASP.Net MVC 2.0 application to 3.0

Recently I converted medium size project to ASP.Net MVC 3.0. In parallel I decided to move from web forms view engine to the Razor as it is wordless and more clean. In this post I would like to share my experience and show problems which I found during migration.

First of all check the following article of Scott Guthrie: Announcing release of ASP.NET MVC 3, IIS Express, SQL CE 4, Web Farm Framework, Orchard, WebMatrix. In the “How to Upgrade Existing Projects” he says:

ASP.NET MVC 3 is compatible with ASP.NET MVC 2 – which means it should be easy to update existing MVC projects to ASP.NET MVC 3

It is generally true for MVC itself. But in real life applications there are a lot of 3rd party libraries which should be also compatible with new MVC version. Also if you perform migration from web forms view engine to Razor there will be another problems. So generally migration process is not so easy and straightforward.

For begin check manual upgrade guide available from here (there is also automated ASP.NET MVC 3 upgrade tool available for download, but I don’t really like these kind of tools because I want to know what I need to do and why I need to do it). Start with the steps described in the manual upgrade guide. Note that there is typo in step 6: instead of System.WebPages.dll you need to add System.Web.WebPages.dll.

After you have performed all steps described in the guide try to compile your application. Most probably 1st compilation will fail. In my case it was caused by incompatibility with another libraries:

MVCContrib assembly version for MVC 3.0 is available for download from codeplex. For MVCFutures (Microsoft.Web.Mvc.dll) the process is not straightforward: I didn’t find binary version available for download on the http://aspnet.codeplex.com. There is ASP.NET MVC 3 Beta release which contains link on ASP.NET MVC 3 Beta Futures. But unfortunately beta version is not compatible with final release of MVC 3. So the only way I found was to download sources from ASP.NET MVC 3 RTM and build it by myself.

At the moment of writing this article MvcSiteMapProvider  was also released for MVC 3.0 (date of release is 4 Feb 2011). When I started migration there were no version for MVC 3 available – fortunately MvcSiteMapProvider for MVC 2 also works in my scenarios.

Telerik extensions are available for MVC 3.0 at the moment of writing this article. Do not forget that for Telerik extensions with Telerik.Web.Mvc.dll you also need to add css and scripts for appropriate version (these files are located in appropriate folder with version number name, e.g. 2010.3.1318).

Now about converting from web forms view engine to Razor. There is Razor converter tool available from Telerik. I recommend to use this tool as it will make most of simple transformations automatically. Although it has problems, so you will still need to perform manual work. I found the following problems during migration:

  • doesn’t convert placeholders correctly
  • If you use strong typed ActionLinks from MVCFutures:
    @Html.ActionLink<RequestController>(c => c.Create(), ...)
    you will need to add parenthesis after @ for each expression by yourself, e.g.:
    @(Html.ActionLink<RequestController>(c => c.Create(), …))
  • ignores Import directives, so you have to do it manually:
    <%@ Import Namespace="UI.Controllers" %>
    to
    @using UI.Controllers
  • doesn’t convert server scripts in html tags and javascript variables, so you will also need to do it manually:
    <img alt="" src="<%= Url.Content("~/content/images/foo.gif") %>" />
    to
    <img alt="" src="@Url.Content("~/content/images/foo.gif")" />

In addition to the mentioned problems you may face with situation that some html helpers which worked for web forms view engine doesn’t work for Razor. E.g. Grid from MVCContrib has known limitation: action syntax is not working for Razor. From twitter conversation with Jeremy Skinner (author of MVCContrib grid): the workaround is to replace action syntax with custom columns.

That’s all what I wanted to share with you in this post. Hope it will help you in your work.

Saturday, February 5, 2011

Get “Manage Hierarchy” and “Approve” role definitions programmatically in Sharepoint

In Sharepoint there are several OTB role definitions which are created during site creation:

Role name Description
Full Control Has full control
Design  Can view, add, update, delete, approve, and customize
Contribute Can view, add, update, and delete list items and documents
Read Can view pages and list items and download documents
Limited Access Can view specific lists, document libraries, list items, folders, or documents when given permissions
Approve Can edit and approve pages, list items, and documents
Manage Hierarchy Can create sites and edit pages, list items, and documents
Restricted Read Can view pages and documents, but cannot view historical versions or user permissions

You can access some of these role definitions via object model using SPRoleType enum using the following mapping table:

Role name SPRoleType member
Full Control SPRoleType.Administrator
Design SPRoleType.WebDesigner
Contribute SPRoleType.Contributor
Read SPRoleType.Reader
Limited Access SPRoleType.Guest
Approve SPRoleType.None
Manage Hierarchy SPRoleType.None
Restricted Read SPRoleType.None

Note that last 3 roles have SPRoleType.None type. It means that you can’t retrieve these role definitions using SPRoleDefinitionCollection.GetByType() method. Restricted Access is special role – it is shown when e.g. user has access on some folder in the list, but not has access to the whole list or web site. In most cases you should not use this role programmatically. But what with “Manage Hierarchy” and “Approve” roles? We also can’t use SPRoleDefinitionCollection.GetByType() method in order to get them programmatically. What options do we have?

The most often approach I found during investigation is to use hardcoded literals for the role name:

   1: string roleName = "Manage Hierarchy";
   2: SPRoleDefinition roleDefinition =
   3:     web.RoleDefinitions.Cast<SPRoleDefinition>().FirstOrDefault(r => r.Name == roleName);

This approach works, but if you have multilingual topology you will have problems, because “Manage Hierarchy” role name will be localized according to the web site name (e.g. on Finnish it will be “Hierarkian hallinta”). The best option would be retrieving of the role definition using the same way as Sharepoint uses.

After some investigation I found Microsoft.SharePoint.Publishing.Internal.StringIds class (defined in the Microsoft.SharePoint.Publishing.dll). It has a field StringIds.RoleNameHierarchyManager which seems like what I looking for. Unfortunately there is no so much information about it, so I used reflector to watch how Sharepoint uses it. Sharepoint uses these strings via internal class Microsoft.SharePoint.Publishing.Internal.Resources in the same Microsoft.SharePoint.Publishing.dll assembly like this (see Microsoft.SharePoint.Publishing.Internal.RootProvisioner.AddSecuritySettings() method):

   1: Resources.GetString("RoleNameHierarchyManager")

If you will investigate Microsoft.SharePoint.Publishing.Internal.Resources class you will find that it uses Microsoft.SharePoint.Publishing.Intl.dll assembly for retrieving resource values (this assembly is located in the GAC).

So in order to retrieve role name by the same way as Sharepoint retrieves it, you have several options:

  • use reflection
  • create custom class which works like internal class Microsoft.SharePoint.Publishing.Internal.Resources

I will show you 2nd variant. You can use the following class in order to retrieve resource strings from Microsoft.SharePoint.Publishing.Intl.dll assembly:

   1: public class PublishingResources
   2: {
   3:     private static ResourceManager resourceManager;
   4:  
   5:     static PublishingResources()
   6:     {
   7:         var resourceAssembly = Assembly.Load("Microsoft.SharePoint.Publishing.intl, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
   8:         resourceManager = new ResourceManager("Microsoft.SharePoint.Publishing.Strings", resourceAssembly);
   9:     }
  10:  
  11:     internal static string GetString(string resourceName, CultureInfo cultureInfo)
  12:     {
  13:         return resourceManager.GetString(resourceName, cultureInfo);
  14:     }
  15: }

Now you are able to retrieve “Manage Hierarchy” role definition programmatically without hardcoding role name:

   1: string roleName = PublishingResources.GetString(StringIds.RoleNameHierarchyManager, web.UICulture);
   2: SPRoleDefinition roleDefinition =
   3:     web.RoleDefinitions.Cast<SPRoleDefinition>().FirstOrDefault(r => r.Name == roleName);

All information from this post is also appliable for “Approve” role definition. Just instead of StringIds.RoleNameHierarchyManager you will need to use StringIds.RoleDescriptionApprover for it.

Tuesday, January 25, 2011

Show reusable data on the master page in Sharepoint

When you create site collection based on OTB publishing portal template, root site contains Reusable Data list. It stores pieces of information (it can be plain or html formatted text) which can be shown on different pages in your portal. In order to show reusable data on the publishing page you need to create or edit existing page and when focus will be on the page content field (rich html field type) in the top ribbon you can choose Insert > Reusable data. It will insert reference on the reusable data list item on your page. So when you will change reusable data list item, it will affect all pages where it is inserted.

However, there can be situations when you need to show reusable data on the master page (i.e. not on publishing pages). It is not so straightforward, because after this you will notice that SPContext.Current.ListItem always contains reference on the publishing page which is shown with you master page. It means that all field controls on the master page will show field values from publishing page, but not from the master page.

In order to show field values from master page I created custom control, which retrieves field value from master page’s metadata and displays it:

   1: public class MasterPageFieldValue : Control
   2: {
   3:     protected override void Render(HtmlTextWriter output)
   4:     {
   5:         try
   6:         {
   7:             if (this.Page.Master == null)
   8:             {
   9:                 return;
  10:             }
  11:             var file = SPContext.Current.Site.RootWeb.GetFile(this.Page.MasterPageFile);
  12:  
  13:             var field = file.Item.Fields.Cast<SPField>().FirstOrDefault(f => f.InternalName == this.FieldName)
  14:             if (field == null)
  15:             {
  16:                 return;
  17:             }
  18:             output.Write(field.GetFieldValueAsHtml(file.Item[this.FieldName]));
  19:         }
  20:         catch (Exception x)
  21:         {
  22:             // log
  23:         }
  24:     }
  25: }

As you can see it gets SPListItem instance for the master page (which is located in Masterpage gallery (_catalogs/masterpage) in most cases) and retrieves field value from its metadata. After that it renders value to output. Notice that it inherits from standard ASP.Net Control class (i.e. not from Sharepoint BaseFieldControl as you may think it should use), because BaseFieldControl uses SPContext.Current.ListItem internally and as I said in our case it is not correct storage for retrieving metadata values.

In order to use this control on you need to add it on the master page by common way:

   1: <%@ Register TagPrefix="uc" Assembly="MyAssembly" %>
   2: ...
   3: <uc:MasterPageFieldValue FieldName="ReusableData" runat="server" />
After that you need to add “ReusableData” field into Publishing Master Page content type (or another content type you use for your master page). Then go to Masterpage gallery (_catalogs/masterpage) and choose Edit properties for your master page. Insert reference on reusable data list item by regular way and check in changes. After this you will have reusable data shown on the master page.

Monday, January 17, 2011

Submit html form using Content Editor Web Part in Sharepoint

Recently I faced with the problem of submitting of html form which should be added on the page using CEWP. The problem is that in most cases master page of Sharepoint web part already has top-level html form:

   1: <form runat="server" onsubmit="return _spFormOnSubmitWrapper();">
   2: ...
   3: </form>

And all placeholders are inserted within this form. So if you try to add your form in page layout or add html layout with form element on the page using standard Content Editor Web Part you will get nested forms which is not well formed html.

Trying to figure out how to solve this issue, I found the following project on Codeplex: http://sharepointformsubmit.codeplex.com. This is single javascript file (jquery.SharePointFormSubmit.js) which uses the following trick: instead of real html form you should add div element in the CEWP. And instead of submit input element you should use button with onclick event handler attached. Script will create form with all input elements dynamically, but not as nested form – as a sibling form – and then will call submit() method on it.

Unfortunately it did work for me as is (at least revision 1 from 2009-04-16 which is available on Codeplex. There is also revision 2 available on code.google.com - http://code.google.com/p/jquerysharepointform, but I didn’t tried it). I was needed to slightly modify the script:

   1: (function($){
   2:     $.fn.SharePointFormSubmit = function(element,method,action) {
   3:         var e; // new input element
   4:         var f = document.createElement("form"); // form
   5:             f.method = method;
   6:             f.action = action;            
   7:             f.setAttribute("style","display:none");
   8:  
   9:             $(element).find("input, select, textarea").each(function(){
  10:                 e = document.createElement("input")
  11:                 e.setAttribute("type", $(this).attr("type"));
  12:                 e.setAttribute("id", $(this).attr("id"));
  13:                 e.setAttribute("name", $(this).attr("name"));
  14:                 e.setAttribute("value", $(this).val());
  15:                 e.setAttribute("checked", $(this).attr("checked"));
  16:                 e.setAttribute("multiple", $(this).attr("multiple"));
  17:                 f.appendChild(e);
  18:             });
  19:  
  20:             var s = document.body.appendChild(f);
  21:             s.submit();
  22:     };
  23: })(jQuery);

Having this script you need to do the following actions in order to submit form from CEWP:

1. Add 1st CEWP on the page with the following content:

   1: <script type="text/javascript"
   2: src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
   3: <script type="text/javascript"
   4: src="http://jquerysharepointform.googlecode.com/files/jquery.SharePointFormSubmit.js">

You can also download these scripts on your web server (e,g, in [12 or 14]/Templates/Layouts folder) and use them from it.

2. Add 2nd CEWP with your “form” (instead of form you need to insert div element, like described here):

   1: <div id="divIdForm">
   2: Email: <input type="text" name="email" id="email"><br />
   3: <input type="submit" value="Submit" onclick="jQuery().SharePointFormSubmit('#divIdForm', 'post',
   4: 'http://example.com')">
   5: </div>

In this example with this code added to the CEWP you will be able to submit forms into “html://example.com” URL. It will work also for cross domain submits.

Monday, January 10, 2011

What to do if Take snapshot option is disabled in VMWare

We often use virtual environments for Sharepoint development (VMWare and VirtualPC are most frequently used virtualization platforms in our case). I also often use snapshot feature of VMWare if some risky configuration changes are required. It is really useful when you can rollback system changes to the last stable snapshot in a few minutes comparing with spending hours on restoring failed system from scratch.

Recently I encountered with strange situation: “Take Snapshot…” option in menu was disabled (grayed):

image

After some investigation I found that hard disk drives were configured in Independent persistent mode (VM > Settings > Hard disk > Advanced):

image

As documentation says:

Independent – For independent disks, the data on the disk is not recorded when you take a snapshot of the virtual machine. If the Independent check box is unavailable, it might mean that the virtual machine currently has snapshots. After you delete the snapshots, the check box becomes available.

I had several HDD – all with independent mode. So I uncheck “Independent” checkbox for all drives, and after that “Take Snapshot…” option became available:

image

It is important to note that you should uncheck “Independent” checkbox for all disks, because if there will be at least one disk in independent mode snapshots will be still disabled. Hope it will economy your time if you will face with similar problem.