Tuesday, February 18, 2014

Custom Actions for list items assigned programmatically


Custom actions are usually deployed in a declarative manner, such as an Elements.xml file. They can be deployed to a specific content type in case your document library inherits from the specific content type, such as below:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  </CustomAction>
  <CustomAction
  Id="{ACE9ADB4-A9CE-4503-9819-AC9C6FC09E01}"
  RegistrationType="ContentType"
  RegistrationId="0x01010059dee241fa9e47e8aded6c595cb2b406"
  Location="EditControlBlock"
  Sequence="101"
  Title="Approve/Reject Monthly Report">
    <UrlAction Url="javascript:MyNamespace.retrieveListItems({ItemId});"/>
  </CustomAction>
  
</Elements>
       
However, when your document library gets deployed via code, such as  feature receiver, and especially if the document library is not associated with a content type, there is the option to attach a custom action programmatically such as  in the example below.           


                    Guid formlibID = web.Lists.Add("My Library", string.Empty, SPListTemplateType.XMLForm);
                    SPList formLib = web.Lists[formlibID];

                    SPUserCustomAction printForm = formLib.UserCustomActions.Add();
                    printForm.Title = "Print Form";
                    printForm.Url = "javascript:MyNamespace.printForm({ItemId},'{ItemUrl}')";
                    printForm.Location = "EditControlBlock";
                    printForm.Update();

In both examples, the custom action performs some javascript logic (in this case the two functions both open up a modal popup), that takes in the current items' out-of-the-box ID and URL. 

Thursday, May 9, 2013

Double AD profiles in User Profile Service?

This is directly related to working with Claims.

The user profile service imports AD accounts and sets the 'identifier' of the object to be the sAMAAccountName (This is the AD property which looks like sampledomain\johndoe).

When a claims web app accesses the User Profile service, such as for example MySites, if configured to use Claims, it looks for the User Profile service by it's own identifier, which is the token in a format like this:  i:0#.f|ldapmember|johndoe

The object will not be found, and MySites will generate the user profile and set the url to the personal site (yet another property in User Profiles), and this is how you end up with two records that refer to the same person, such as below:


The first one is the one generated by the User Profile Service, with properties such as First name, Last Name and other properties you have mapped to be imported from AD, while the other profile is the one generated by MySites, and only has the token and the URL set to the personal site.

The solution is to map the two identifiers to each-other so that when a claims-based app queries the User Profile service, it finds the profile by token.


The first property, Claim User Identifier, refers to the token and the mapped AD property called sAMAAccountName refers to the format domain\user.

Once mapped, any user profile action will follow this rule. For already existing duplicate accounts, the duplicate token one needs to be deleted for clean-up.

The new MySite generations should look like this:


This solution assumes that when you set up the UPS AD connection, you configure it as well as Claims:


Saturday, May 4, 2013

Migrating CSV data into SharePoint lists

A common migration scenario of data into SharePoint involves CSV files to be imported via Datasheet view into SharePoint lists.

However there are some limitations such as multi-lookup values, where data should be provided in the following format: "5;#technology;#3science;#". The same format has to be provided for columns of type 'People or Groups', such as "67;#John Doe;#123;#Anne Jackson;#". As a result, you cannot use DataSheet view and you have to upload the CSV programmatically.

This post focuses on reading the CSV file programmatically. You have two options:
  • if you use Powershell, you can use the PS command import-csv and then access the data like this:

    import-csv c:\folder\file.csv
    foreach($line in $excelFile){$title = $line.name 

  • if you use C# you can read the CSV file via a OLEDB driver you need to install locally, to permit your 64-bit code (you have to run on 64 bit to be able to execute SharePoint API calls for the actual lookup of values and list item creation).

    You can find the driver here Microsoft Access Database Engine 2010 Redistributable.
    This driver is simply a replacement for JET OLEDB to be run on server applications. JET is only running on 32 bits.

    string connectionString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=c:\folder_name\;Extended Properties='text;HDR=Yes;FMT=Delimited'";

    string excelSheetName = "file.csv";

    OleDbConnection myConnection = new OleDbConnection(connectionString);
    myConnection.Open();
    OleDbCommand myCommand = new OleDbCommand("Select * from " + excelSheetName + ";", myConnection);
    OleDbDataAdapter adapter = new OleDbDataAdapter(myCommand);
    adapter.Fill(ds);

    myConnection.Close();



Sunday, January 13, 2013

How to use SharePoint's OOB error page to show a friendly error message

In many cases, such as a feature activation, it is best to not overwrite the out-of-the-box error page with a custom one, but have the page show a custom message such that users know where the custom code you wrote, failed.

Envision the following scenario: you have an OOB site collection and on custom feature activation, you try to:

  • bind custom fields deployed by the feature to a metadata term store
  • add these custom fields to content types
  • create subsites programmatically
  • set permissions programmatically, etc.
Wrap each action in it's own try-catch statement and inside the catch call your custom Error Handler  method that writes to the ULS logs.

In addition to writing to the ULS logs, have your method also do a forced Response.Redirect to the OOB error page, with "ErrorText" in the QueryString object:

  //write to log first, then:

   string idcurr = CorrelationId.GetCurrentCorrelationToken().ToString();
   errorMessage = HttpUtility.UrlEncode(errorMessage);
   HttpContext.Current.Response.Redirect("/_layouts/error.aspx?ErrorText="+ errorMessage + "&ErrorCorrelationId=" + idcurr);


The ErrorCorrelationId QueryString parameter will ensure that the ID you see on the error page matches what you see in the ULS logs.
But the actual Error Handling, if custom, will generate it's own ID when writing to the logs.
It is necessary for these two to match, basically for the code to associate the custom Error message generated with the ID shown on the page.
The only way to grab the latest CorrelationID generated is to grab it via this class, and I am referencing this blog where I found the solution to the issue:


public class CorrelationId
    {
        [DllImport("advapi32.dll")]
        public static extern uint EventActivityIdControl(uint controlCode, ref  Guid activityId);
        public const uint EVENT_ACTIVITY_CTRL_GET_ID = 1;
        public static Guid GetCurrentCorrelationToken()
        {
            Guid g = Guid.Empty;
            EventActivityIdControl(EVENT_ACTIVITY_CTRL_GET_ID, ref  g);
            return g;
        }
    }


Thursday, May 17, 2012

Authenticating on a web application with ACS


  1. Connect to ACS. You need to be made a co-Administrator to be able to create a new Access Service instance. Create a new one. Call it something relevant (namespace).  Once it is created, if you try to access it from the “Access Control Service” button in the ribbon, you might get a 403 error. The original Administrator of the Azure account needs to go in and under the new ACS instance, User Management, add you as an admin. Only then will you see the options.
  2. Create a new Relying party. Add the realm and return address to be the new Azure web app url (with https), leave the certify as default, let it create a new rule group. Leave all as defaults.
  3. The identity provider is Windows Live Identity Provider- this is already there out-of-the-box. You are good to go with that. This means you will log in with your Microsoft Live Id.
  4. Edit the rule group, make sure you add a new group for “nameidentifier”.
  5. Set up the web application for ACS:
  • You need Identity Foundation SDK 4 installed. Once installed, the FedUtil (old utility) will be part of Visual Studio. When clicking a web project, you will have a new option in the Tools menu called “Add STS reference”. Add the STS reference. Set the path to its own web.config(it will try to update as a result), set the URL to be the https url of the Azure web app url, add an existing STS by referencing its metadata file. You can get the metadata file from the ACS menu:
  • This adds a new section to the web.config file of the web application, called “Microsoft.IdentityModel”. This new section contains the url of the ACS (Access Control Service Instance you have to set up), the URL of the web application (https://servername), and other data.




Wednesday, May 16, 2012

Installing and setting up PowerPivot for SharePoint 2010


  1. 1. Log into the SharePoint 2010 central admin server as a farm administrator (whatever server Central Admin is installed on)
    2. Install PowerPivot from the SQL 2008 R2 Enterprise disk:
    THIS INSTALL REQUIRES AT LEAST ONE RESTART OF THE MACHINE
    THIS INSTALL REQUIRES ENTERPRISE

    2.1.  Click the Installation option on the left had side and then select New installation or add features to an existing installation
    2.2.  At the Setup Role screen select SQL Server PowerPivot for SharePoint and select your existing farm installation and click Next
    2.3.  Click Next on the Feature Selection screen (this is read only and for information only)
    2.4.  Setup will now run a rule check, if you get any errors you need to resolve these before you can continue – this should be bypassed now if logged in as farm admin and on the CA machine
    2.5.  Leave the name as is: “POWERPIVOT”
    2.6.  Select an account to run the Analysis Services and click Next. Type in the domain account you used as the app pool service account when you installed the DB engine of the instance that runs SharePoint. (say you have a sql instance that is the sql used by SP, go to configuration manager of that sql instance and look at the service account that was used for the DB engine)
    2.7.  On the summary screen click Install. Please check the summary screen first. It should display the correct Central Administration port. If not, you need to go to the installation configuration file (in the textbox at the bottom of the summary screen), open with Notepad and edit to the right port.
    2.8.  Before hitting install, please check this article. If you install on a CA server without a previous SQL installation, you should not run into errors, but check the 3 issues out of this article anyway before proceeding.
            Look at the red sections HACK #1, 2 and 3.
    THIS INSTALL REQUIRES A RESTART OF THE MACHINE

    3. Configure the farm for PowerPivot:
    3.1.  If the install has succeeded and it says so at the end of it, if you navigate to Central Administration, under ‘Manage Farm Features’, you should see :

    3.2.  Under System Settings, farm solutions you should see the following solutions automatically deployed:

    3.3.  Deploy the powerpivotwebapp.wsp to your web application.
    3.4.  System Settings | Manage Services on Server start the required services:
    ·         Excel Calculation Services 
    ·         Secure Store Service 
    ·         Claims to Windows token Service 

    3.5.  Under “Manage service applications”, create a new service application of type “SQL Server PowerPivot”, like in the screen below. Use all defaults.























    3.6.   Check to see if you have Excel Services enabled by seeing if it is listed in the Service Applications section of central admin. If not enabled, enable it by creating one. Use all defaults.
    3.7.   If there is no listing for a secure store then click on New and select Secure Store Service. Use defaults. Click on Generate New Key and complete the details (I this you should be all set on this one, we generated the key for other purposes in our last session)
    4.     Enable PowerPivot for the site
    4.1.  Site collection features->activate powerpivot:


    4.2.  This should deploy the new document library template:


    4.3.  When creating a PP library instance, you might get this:


    To resolve this, you need to add 
    <SafeControl Src=”~/_layouts/powerpivot/*” IncludeSubFolders=”True” Safe=”True” AllowRemoteDesigner=”True” SafeAgainstScript=”True” />” 
    to the site collection web config manually.


    5.     Additional  setup you need to perform:
    5.1.  Go to your site collection, try to export a list as a datafeed, and if you get this error:
    For security reasons DTD is prohibited in this XML document. To enable DTD processing set the ProhibitDtd property on XmlReaderSettings to false and pass the settings into XmlReader.Create method.
                                    It means you do not have ADO.NET for 3.5 (really just KB 982307) installed. You need to follow this article on MSDN (below) and install on the web server that has the web app that serves your site collection. If farm, on all web servers.

    THIS REQUIRES ANOTHER RESTART
    5.2.  Double-hop issue: you get prompted on data refresh of the powerpivot charts; you need to follow the steps from this article:



Classic mode to Claims mode migration


On an existing web application that is configured o run in Classic Mode authentication, if NTLM is the authentication provider, if you want to switch to Claims, you need to run the following script:


$WebAppName = "http://serverurl"
$account =
"domain\administrator"
$wa = get-SPWebApplication $WebAppName

Set-SPwebApplication $wa -AuthenticationProvider (New-SPAuthenticationProvider) -Zone Default

--wait here for the prompt to migrate users and answer YES

$wa = get-SPWebApplication $WebAppName
$account = (New-SPClaimsPrincipal -identity $account -identitytype 1).ToEncodedString()
$zp = $wa.ZonePolicies("Default")
$p = $zp.Add($account,"PSPolicy")
$fc=$wa.PolicyRoles.GetSpecialRole("FullControl")
$p.PolicyRoleBindings.Add($fc)
$wa.Update()
$wa = get-SPWebApplication $WebAppName
$wa.MigrateUsers($true)

In order to test if this has gone through:
  • check the web.config, if the new Membership Provider section for Claims has been added
  • log in as a site user, make sure that if you navigate to My Settings, you see the token format of the user, rather than domain\user
  • go to Central Administration and check that the Default Zone is set to Claims
  • in Central Administration, make sure in User Profiles that there are no duplicate accounts (the migration part of this script should take care of this)