Showing posts with label feature. Show all posts
Showing posts with label feature. Show all posts

Wednesday, June 9, 2010

Update 12 Hive Through A Feature

As we got our SharePoint environment up and running, we needed to have the ability to make changes to our 12-Hive. We also wanted to have the flexibility to undo the changes and restore the 12-Hive to the way it was originally.

This post discusses the scenario where you need to make changes to files that already exist in the 12-Hive. I call this solution HiveUpdates. If you have a need to add new files to the 12-Hive that do not already exist, such as new images or layouts, check out my other article Add New Files To 12-Hive Through A Solution. I call that solution HiveAdditions. We decided it would be best for us to separate these two types of Hive changes into two separate solutions.

This solution, HiveUpdates, allows us to modify existing Hive files. This solution contains a feature that, when activated, makes a backup copy of the original file, then overwrites the original with the new file. When the feature is deactivated, it copies the backup file back to the original filename.

The method I used is based on the article 12 Hive System-File Changes: One Feature to rule them all!. The above post has a routine that is called when the feature is activated or when its deactivated. The code that fires simply "swaps" newly changed 12-hive files with their corresponding original 12-hive files. This happens both when the feature is activated or deactivated. While the "swap" code seems like a good idea on the surface (the same code that deploys the changes will swap files back to undeploy them), I found in practice this seemed risky and even prone to error if something went wrong on the feature activate or feature deactivate. I sometimes got the files out of sync and would lose the original 12-Hive versions.

Therefore, I modified the code to first make a backup copy of a 12-Hive file I want to update, then overwrite the original 12-hive file with my changed version (this is an activate). On a deactivate, it overwrites my changed version with the original's backup file. The original's backup file always stays there as a backup, which makes me feel very safe. And I like feeling safe. I like my job and want to keep it.

1. In Visual Studio 2008, File -> New -> Project

2. Under Visual C#, pick SharePoint, then Empty

3. For Name, type: HiveUpdates

4. For Location, type: D:\development

5. Click OK

6. Select Full Trust (Deploy to GAC) and click OK

7. In WSP View (View -> Other Windows -> WSP View), click "Create new feature"

8. Change Feature Scope to Farm

9. Check Add feature receiver

10. click OK

11. In Solution Explorer, rename Feature1 to HiveUpdates

12. Rename Feature1Receiver.cs to HiveUpdatesReceiver.cs

13. Double-click HiveUpdatesReceiver.cs to open it

14. Rename class from FeatureReceiver1 to HiveUpdatesReceiver

15. Below that, you'll see the constructor "public FeatureReceiver1". Rename constructor from FeatureReceiver1 to HiveUpdatesReceiver.

16. In WSP View, hit refresh and notice FeatureReceiver1 is now HiveUpdatesReceiver

17. Rename Feature1 folder to HiveUpdates

Make the following code modifications/additions to HiveUpdatesReceiver.cs:

18. At the top of the code, add the following using statements:
using Microsoft.SharePoint.Administration;
using System.IO;

19. Above the HiveUpdatesReceiver constructor, add the following:
private DirectoryInfo localDirectory = new DirectoryInfo(@"C:\");

20. In the FeatureActivated method, add:
DeployToHive(properties);

21. In the FeatureDeactivating method, add:
RemoveFromHive(properties);

22. Below the FeatureUninstalling method, add the following 4 methods (DeployToHive, RemoveFromHive, GetHiveFile, GetLocalDirectory):
private void DeployToHive(SPFeatureReceiverProperties properties)
{
//loop through each server in the farm
foreach (SPServer server in properties.Definition.Farm.Servers)
{
//if the current server is a WebFrontEnd server
if (server.Role == SPServerRole.WebFrontEnd || server.Role == SPServerRole.Application
|| server.Role == SPServerRole.SingleServer)
{
string localFilePath = GetLocalDirectory(properties.Definition.RootDirectory,
server.Name);
localDirectory = new DirectoryInfo(localFilePath);

foreach (FileInfo fileinfo in localDirectory.GetFiles("*",
SearchOption.AllDirectories))
{
//if current file in feature exists in 12 hive on current server
if (File.Exists(GetHiveFile(fileinfo.FullName, server.Name))
&& (!fileinfo.FullName.Contains(".hivebackup")))
// backup file from 12 hive to feature
File.Copy(GetHiveFile(fileinfo.FullName, server.Name),
fileinfo.FullName + ".hivebackup", true);

//if current file in feature exists
if (File.Exists(fileinfo.FullName) &&
(!fileinfo.FullName.Contains(".hivebackup")))
// copy local file to the 12 hive
File.Copy(fileinfo.FullName, GetHiveFile(fileinfo.FullName, server.Name),
true);
}
}
}
}



private void RemoveFromHive(SPFeatureReceiverProperties properties)
{
//loop through each server in the farm
foreach (SPServer server in properties.Definition.Farm.Servers)
{
//if the current server is a WebFrontEnd server
if (server.Role == SPServerRole.WebFrontEnd
|| server.Role == SPServerRole.Application
|| server.Role == SPServerRole.SingleServer)
{
string localFilePath = GetLocalDirectory(properties.Definition.RootDirectory,
server.Name);
localDirectory = new DirectoryInfo(localFilePath);

foreach (FileInfo fileinfo in localDirectory.GetFiles("*",
SearchOption.AllDirectories))
{
//if current backup file in feature exists in 12 hive
if (File.Exists(fileinfo.FullName + ".hivebackup"))
// copy hivebackup file back to the 12 hive
File.Copy(fileinfo.FullName + ".hivebackup",
GetHiveFile(fileinfo.FullName, server.Name), true);
}
}
}
}



string GetHiveFile(string fileName, string serverName)
{
if (fileName.StartsWith(localDirectory.FullName))
{ // file is local
string p = @"\\" + serverName
+ @"\C$\Program Files\Common Files\Microsoft Shared\web server extensions\12"
+ fileName.Remove(0, localDirectory.FullName.Length);
return p;
}
throw new Exception("Filepath doesn't point to correct local hive!");
}



string GetLocalDirectory(string filePath, string serverName)
{
return @"\\" + serverName + @"\" + @filePath.Replace(":", "$") + @"\12";

//write a class that writes exceptions to the event log
throw new Exception("Filepath doesn't point to correct local hive!");
}

Now we need to create the appropriate 12-Hive file structure for the feature in Solution Explorer. Any 12-Hive files we want to update will have their newer versions placed in the file structure we are about to create.

23. In Solution Explorer, right-click project HiveUpdates, then click Add -> New Item... -> SharePoint -> Template.

24. You will notice a folder called Templates was created in the Solution Explorer. Delete the default file TemplateFile1.txt.

25. Under Templates, create a folder called FEATURES.

26. Under FEATURES, create a folder called HiveUpdates.

So far, we have created 12/TEMPLATE/FEATURES/HiveUpdates. This is the location in the 12-Hive where that feature's feature.xml gets deployed to. But now, we must create another 12-Hive structure under this folder. This is where our newer files will be deployed to when the solution is initially deployed. When the feature is activated, this is where the above code will store backups of the original files from the 12-Hive, as well as the source from which to overwrite the 12-Hive files with.

27. Under HiveUpdates, create a folder called 12.

28. Under 12, create a folder called TEMPLATE.

29. Under TEMPLATE, create a folder called LAYOUTS.

30. Copy application.master in here. Either make some small change to it or at the very least change the date on the file so its different from what it is in the 12-Hive. Be sure to right-click application.master and click Include In Project if it is not already included.

31. In WSP View, double-click feature.xml.

32. Change the value of Title from Feature1 to HiveUpdates.

33. Verify Scope is set to Farm.

33. After the title or scope, add: ActivateOnDefault="false"

34. Verify your manifest was created correctly. In WSP View, Under HiveUpdates, you should see manifest.xml. Double-click manifest.xml and verify your file path for application.master is FEATURES\HiveUpdates\12\TEMPLATE\LAYOUTS.

35. click Build -> Rebuild Solution

36. click Build -> Package Solution

The solution package that you just created in the above step, HiveUpdates.wsp, was created in your project's bin\debug folder.

To deploy the solution:

37. create folder d:\deploy

38. copy HiveUpdates.wsp from project's bin\debug folder to d:\deploy

39. In d:\deploy, create two batch files, add_hiveupdates.bat and remove_hiveupdates.bat.

add_hiveupdates.bat:
c:

cd\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN

stsadm -o addsolution -filename d:\deploy\HiveUpdates\HiveUpdates.wsp

stsadm -o deploysolution -name HiveUpdates.wsp -immediate -allowGacDeployment

pause

stsadm -o activatefeature -name HiveUpdates

d:

remove_hiveupdates.bat:
c:

cd\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN

stsadm -o deactivatefeature -name HiveUpdates

stsadm -o retractsolution -name HiveUpdates.wsp -immediate

pause

stsadm -o deletesolution -name HiveUpdates.wsp

d:

40. in d:\deploy, type add_hiveupdates.wsp and hit enter

41. verify solution was deployed in Central Administration -> Operations -> Solution Management

42. verify files were added to 12-Hive!

Tuesday, August 25, 2009

How to deploy SharePoint list templates

Ok, so after I had packaged up three web parts into their own features in a solution (wsp) file, I had one more issue to address: those three web parts were dependent on lists created from two list templates. So I needed to also package up those list templates as features in the solution file.

After finding countless pages that explain how to do it the hard way (and there are many hard ways), I finally realized one of my favorite sites showed me the easiest, best way, by far. Jeremy Thake of SharePointDevWiki created a video showing how its done. Jeremy is the man downunder! Excuse me, the bloke downunder.

In addition, I also needed to package up two list templates into the solution package, not just one as is done in the video. As with most stuff SharePoint, once you know how to do something, its extremely easy to do.

At a high level, here is what we will do:
  1. Create some lists in your SharePoint site.

  2. Install SPSource from CodePlex and use it to reverse engineer the lists you created on your SharePoint site back into list templates, storing the necessary files that make up the list templates into a Visual Studio project.

  3. Use BuildWSP to build your WSP!
Note: in this example, I will create a new Visual Studio project. However, I want to add the reverse-engineered list templates into my existing project with my web parts. To do this, all I have to do is copy the contents of my FEATURES folder from my new Visual Studio project (where I reverse engineered my list templates) into my existing Visual Studio project's FEATURES folder (where my web parts already are). When I run BuildWSP, it will just simply see the new list template features and add those to the .wsp. I will not show the copy in the steps below because I don't know if you need to add your list templates to a pre-existing project, but like I said, just copy the list templates from the FEATURES folder if you want to do this.

Ok, so now for a little bit of detail.
  1. In your SharePoint site, create two lists. I will call one Contact List Template and the other Sent Emails Template in this example. Add some columns to them.


  2. Download SPSource from CodePlex. I put spsource.exe in a folder called d:\deploy, but just put it somewhere easy to get to.


  3. If you haven't already downloaded and installed WSPBuilder, do it. WSPBuilder will be added under the Tools menu in Visual Studio.


  4. Create the Visual Studio project that will be the destination for the reverse-engineered list templates.
    1. In Visual Studio, click File -> New -> Project -> WSPBuilder -> WSPBuilder Project

    2. Name it MyListTemplates and click OK.

  5. Add a feature for each list template you wish to reverse engineer. (Note: Trying to add both list templates to the same feature has proven to be problematic. If you add them to the same feature, when you deploy the feature and create lists from the list templates, all the lists you create will only use the definition from the first list template that was deployed, even if you create a list based on the second list template. Therefore, create one feature for each list template!)
    1. In the Solution Explorer, right-click the project, then click Add -> New Item -> WSPBuilder -> Blank Feature

    2. Name it ContactListTemplate and click Add.

    3. In the Feature Settings dialog, change the Scope to Site and click OK.

    4. Do a. through c. again, but this time name the feature SentEmailsTemplate.

    5. Notice both features have been added under /12/TEMPLATE/FEATURES/ in your Solution Explorer. Each contains an elements.xml and feature.xml.

  6. Create a .spsource file for each feature. These files tell SPSource what lists to reverse engineer into list templates.
    1. In Solution Explorer, right-click the ContactListTemplate feature, then click Add -> New Item -> Common -> XML File, name it contactlist.spsource and click Add.
    2. In Solution Explorer, right-click the SentEmailsTemplate feature, then click Add -> New Item -> Common -> XML File, name it contactlist.spsource and click Add.

    3. Copy the contents of elements.xml into both of these .spsource files.

    4. In contactlist.spsource, add the following inside the Elements tag:
      <ListTemplate Name="Contact List Template" />
    5. In sentemails.spsource, add the following inside the Elements tag:
      <ListTemplate Name="Sent Emails Template" />
  7. In your project's folder, create a text file called SPSource.cmd with these contents:
    @ECHO OFF

    SET SPSOURCE="d:\deploy\SPSource.exe"
    SET DEVSITEURL="http://sharepoint2007:1001/marketing"

    %SPSOURCE% -designsite %DEVSITEURL%

    PAUSE
    Be sure to set DEVSITEURL to your SharePoint site where you created the lists back in step 1.


  8. Double-click SPSource.cmd so it runs. If you get an error message saying something like "Method not found...SPOpenBinaryOptions...), you need to install Windows SharePoint Services 3.0 Service Pack 1 (SP1). This is because SPSource requires methods that are in WSS 3.0 SP1.

  9. In Solution Explorer, click Show All Files. Under the two features, you'll see a new folder under each with the same name. Right-click the new folders and click Include In Project.


  10. Build the project.


  11. Create the wsp solution file by clicking Tools -> WSP Builder -> Build WSP.


  12. You can now deploy MyListTemplates.wsp with stsadm as you normally deploy wsps! Use addsolution, deploysolution, and activatefeature to deploy however you want to deploy it (ie using GAC or CAS, etc).

Thursday, July 23, 2009

How to deploy a SharePoint web part to bin

As I was learning how to deploy my web parts as features in a solution package .wsp (I used WSPBuilder to create the .wsp), I realized I needed to make sure they were deployed not to the GAC (which is the default), but rather to the web application's /bin folder (under the web application in IIS). Bin is preferred over GAC because of security reasons: deploying to GAC unnecessarily exposes the web part's assembly .dll to every application on the server.

This post will show you how to deploy a web part to the /bin folder (using WSPBuilder) AND how to configure it using CAS (Code Access Security) so your web part can actually run from that folder too! I kept finding articles that discuss one or the other, but not both. Those articles that did help me quite a bit have been referenced in the last part of this post.

So, from the top, when you create a web part in Visual Studio 2008 and hit F5, the web part will deploy to the site you selected (right-click Project's Properties, click Debug, then whatever site you typed in Start browser with URL). In your Solution Explorer, you will notice a folder called /bin with another folder called /debug under that, and the compiled code will sit under there. This means its being deployed into the GAC.

At a high level, there's really two steps we must do to get our web part to run from the web application's (your SharePoint site's) /bin folder. (You can get to /bin either by going into IIS and choosing the web application you want it deployed to, or by going to c:\inetpub\wwwroot\wss\VirtualDirectories\<web application>\bin.)

The first step is actually deploying to the above-mentioned location. Once this is done, sure, the web part is right where you want it to be, but it won't run until we do step two, which involves setting the proper permissions to allow it to run from that spot. This second step involves modifying the web.config to look at a custom policy file where we will make changes to the CAS policies.


First, deploy the web part assembly to the /bin:



1. In Solution Explorer, delete the /bin folder. It will reappear in a few seconds with a Debug subfolder, but it will be empty other than that.

2. In Solution Explorer, create a folder called 80, and under that, create a folder called bin. So you will have /80/bin.

3. Right-click the project in Solution Explorer, and on the Compile tab, change Build Output Path from bin\Debug\ to 80\bin\.

4. In Solution Explorer, in AssemblyInfo.vb, you must add two lines:
a. below the other Imports statements, add the following:
Imports System.Security
(Note: this will be a Using statement if you are doing C#)

b. Below one of the other Assembly statements, add the following:
<assembly: AllowPartiallyTrustedCallers()>
(Note: This step doesn't affect the deployment of the web part, but comes into play when you try to actually use the web part on a page after its been deployed to /bin.)

5. Now, when you compile the application you will notice the assembly (the dll) appears in /80/bin instead of where it was being put before, /bin/debug. If you hit F5 to deploy it, you will notice all the other deployment files Visual Studio creates is also placed in this folder. This leads me to my next point.

Visual Studio creating deployment files automatically is no good. Sure, it will get everything up and running on the server, but maybe not the way you want. For instance, even though I have created a /12/TEMPLATE/FEATURES/AddContactFromAD folder structure in my Solution Explorer, and put in my feature.xml, elements.xml (mine is actually called AddContactFromAD.xml), and a web part definitions file AddContactFromAD.webpart, Visual Studio generates its own feature.xml (which doesn't have my pretty description I put in the real feature.xml). It also tends to write its own manifest.xml, wanting to deploy to GAC! So, let's deploy a much better way, using WSPBuilder! You can download WSPBuilder here. Once installed, it will be added to Visual Studio's Tools menu.

6. Assuming you have the above-mentioned 12 hive folder structure with a folder for each web part under FEATURES, and with that, a feature.xml, elements.xml, and .webpart file under each web part's folder, you can simply click Tools -> WSPBuilder -> Build WSP. WSP Builder will create the resulting .wsp and drop it in your project folder. Renaming it to .cab and opening it you will see each web part's feature.xml, elements.xml, .webpart, plus the assembly .dll and an autogenerated manifest.xml. Looking inside the manifest.xml will reveal two things: 1. the DeploymentTarget is WebApplication (no longer GlobalAssemblyCache as it was before), and 2. CAS policy permissions were added.

7. Ok, so let's deploy this bad boy. To do this, I add the solution, deploy the solution, then activate the features (1 feature per web part). Below is my own batch file add.bat:
stsadm -o addsolution -filename contactuswebpart.wsp

stsadm -o deploysolution -name contactuswebpart.wsp -url http://sharepoint2007:1001/ -immediate -allowCasPolicies

stsadm -o activatefeature -name contactlist -url http://sharepoint2007:1001

stsadm -o activatefeature -name emailcontact -url http://sharepoint2007:1001

stsadm -o activatefeature -name addcontactfromad -url http://sharepoint2007:1001


Also, my remove.bat comes in handy when testing:
stsadm -o deactivatefeature -name contactlist -url http://sharepoint2007:1001

stsadm -o deactivatefeature -name emailcontact -url http://sharepoint2007:1001

stsadm -o deactivatefeature -name addcontactfromad -url http://sharepoint2007:1001

stsadm -o retractsolution -name contactuswebpart.wsp -url http://sharepoint2007:1001/ -immediate

stsadm -o deletesolution -name contactuswebpart.wsp


At this point, if you check your web application's /bin, you should see the assembly .dll there. If you go to the Web Part Gallery and click the web part, or if you attempt to add the web part to a page, you will get an error. This is because we still need to do Part 2 of this grand adventure: modify the permissions.


For the second and final step, set the CAS policy permissions to enable the web part to run from /bin:



1. We are going to copy the existing wss_minimaltrust.config, rename it, then make our policy changes in that file. Go to c:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG, copy wss_minimaltrust.config and rename it wss_webpartbintrust.config.


2. In our new CAS policy file, wss_webpartbintrust.config, we have three changes.

a. First, add a reference to SharePointPermission in the <SecurityClasses> section. Add the following:
<SecurityClass Name="SharePointPermission" Description="Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />


b. Next, add a new custom permission set. Find the NamedPermissionSet that is named SPRestricted and copy and paste this below it:
<PermissionSet class="NamedPermissionSet" version="1" Name="WebPartBinTrust">
<IPermission class="AspNetHostingPermission" version="1" Level="Minimal" />
<IPermission class="SecurityPermission" version="1" Flags="Execution" />
<IPermission class="WebPartPermission" version="1" Connections="True" />
<IPermission class="SharePointPermission" version="1" ObjectModel="True" />
</PermissionSet>


c. Finally, add the code group. This will indicate when the permissions are to be set. It should be noted this can be done one of two ways, either using a strong name membership (assuming your assembly has been strongnamed) or a url membership, which would give you the option of having the permissions apply to all assemblies in the specified bin or the specific assembly itself.

Under <CodeGroup class="FirstMatchCodeGroup"...> you will see several <CodeGroup class="UnionCodeGroup"...> tags. Place the following code as the first one of these.
<CodeGroup class="UnionCodeGroup" version="1" PermissionSetName="WebPartBinTrust">
<IMembershipCondition class="StrongNameMembershipCondition" version="1" PublicKeyBlob="00240000048000009400000006020000002400005253413100040000010001000DAF8ED8D945CD2ABB2EE7953A6039B791A725F11B4588AC6D70B3E0648F955E9ED4C3C43CB044B8B0E8A6FF4D4FFBE9E3B9297D45F688A7264534E12414E17539305207EC961DA94DF294E7722CCD9BDBFC95A896E996F57156705D281EC39280BD604E87724556AF5807D146963F19F5B43DB69E1F22695463153A553260D2" Name="ContactUsWebPart" />
</CodeGroup>
(Note: The PublicKeyBlob must be the public key blob of your assembly. You may see the PublicKeyBlob in another tag referencing your assembly, in which case, just copy and paste it. Otherwise, you will need to run the command: secutil -hex -s contactuswebpart.dll > publickeyblob.txt, then copy and paste it from the output file publickeyblob.txt.)


3. Almost there... Just two more changes, but this time in your site's web.config. Go to c:\inetpub\wwwroot\wss\VirtualDirectories\<your site>\web.config.

a. In the SecurityPolicy tag, make sure there is an entry for a custom TrustLevel that points to the policy file we just edited:
<trustLevel name="WSS_Custom" policyFile="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\config\wss_webpartbintrust.config" />

b. Scroll down a bit and make sure the trust level is set like this:
<trust level="WSS_Custom" originUrl="" />


That's it! Restart IIS and have fun!


Some good pages that helped me figure this all out:

WSPBuilder - Walkthrough of the Visual Studio Add-in

MOSS 2007 and Code Access Security

Gaurav Taneja article on bin and GAC deployment

Deploy dlls to the webApplications bin rather than GAC

Code Access Security in SharePoint 2007 for Administrators