Aptillon Blog
15May/12

International SharePoint Conference 2012 Follow-up

Posted by Gary Lapointe

16Mar/12

Exporting and Importing SharePoint 2010 Terms

Posted by Gary Lapointe

Ever had the need to migrate Terms from the Managed Metadata Term Store from one environment to another? Do you find the flat, CSV, import approach provided out of the box to be insufficient (especially with its lack of support for alternate labels)? When I first started working with Terms over two years ago I was extremely frustrated by the lack of export and import capabilities so I decided to solve the problem myself by creating two cmdlets, Export-SPTerms and Import-SPTerms. I’ve actually had these cmdlets publicly available for about two years now but I suspect very few people actually know they exist so I thought I’d put together this short post just to highlight them (I also recently pushed out an update which adds support for Site Collection scoped Groups if you have SP1 deployed).

I don’t want to go into a lot of detail regarding how these cmdlets work as they’re really very simple and the bulk of the code is just about iterating through the Term Store structure and turning the various objects into an XML structure (you can download the code from my downloads page). So with brevity in mind, here’s the full help for the Export-SPTerms cmdlet:

NAME
    Export-SPTerms
   
SYNOPSIS
    Export the Managed Metadata Terms.
   
SYNTAX
    Export-SPTerms [-TaxonomySession] <SPTaxonomySessionPipeBind> [[-OutputFile] <String>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
    Export-SPTerms [-TermStore] <SPTaxonomyTermStorePipeBind> [[-OutputFile] <String>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
    Export-SPTerms [-Group] <SPTaxonomyGroupPipeBind> [[-OutputFile] <String>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
    Export-SPTerms [-TermSet] <SPTaxonomyTermSetPipeBind> [[-OutputFile] <String>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
    Export-SPTerms [-Term] <SPTaxonomyTermPipeBind> [[-OutputFile] <String>] [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
   
DESCRIPTION
    Export the Managed Metadata Terms.
   
    Copyright 2011 Falchion Consulting, LLC
    > For more information on this cmdlet and others:
    >http://blog.falchionconsulting.com/
    > Use of this cmdlet is at your own risk.
    > Gary Lapointe assumes no liability.
   

PARAMETERS
    -TaxonomySession <SPTaxonomySessionPipeBind>
        The TaxonomySession object containing the Term Stores to export.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
       
    -TermStore <SPTaxonomyTermStorePipeBind>
        The TermStore object containing the terms to export.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false
       
    -Group <SPTaxonomyGroupPipeBind>
        The Group object containing the terms to export.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false
       
    -TermSet <SPTaxonomyTermSetPipeBind>
        The TermSet object containing the terms to export.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false
       
    -Term <SPTaxonomyTermPipeBind>
        The Term object containing the terms to export.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false
       
    -OutputFile [<String>]
        The path to the file to save the terms to.
       
        Required?                    false
        Position?                    2
        Default value               
        Accept pipeline input?       false
        Accept wildcard characters?  false
       
    -AssignmentCollection [<SPAssignmentCollection>]
        Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite, can use large amounts of memory and use of these objects in Windows PowerShell scripts requires proper memory management. Using the SPAssignment object, you can assign objects to a variable and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment collection or the Global parameter is not used.
       
        When the Global parameter is used, all objects are contained in the global store. If objects are not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory scenario can occur.
       
        Required?                    false
        Position?                    named
        Default value               
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
       
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, type,
        "get-help about_commonparameters".
   
INPUTS
   
   
   
OUTPUTS
   
   
   
NOTES
   
   
    For more information, type "Get-Help Export-SPTerms -detailed". For technical information, type "Get-Help Export-SPTerms -full".
   
    ------------------EXAMPLE 1-----------------------
   
    PS C:\> Export-SPTerms -TaxonomySession "http://site/" -OutputFile "c:\terms.xml"
   
   
    This example exports the terms for all term stores associated with the site and saves to c:\terms.xml.
   
    ------------------EXAMPLE 2-----------------------
   
    PS C:\> Export-SPTerms -Group (Get-SPTaxonomySession -Site "http://site/").TermStores[0].Groups[0] -OutputFile "c:\terms.xml"
   
   
    This example exports the first Group of the first Term Store and saves to c:\terms.xml.
   
   
RELATED LINKS
    Import-SPTerms
    Get-SPTaxonomySession

 

Now lets look at the Import-SPTerms cmdlet (it’s very similar):


NAME
    Import-SPTerms
   
SYNOPSIS
    Import the Managed Metadata Terms.
   
SYNTAX
    Import-SPTerms [-TaxonomySession] <SPTaxonomySessionPipeBind> [-InputFile] <XmlDocumentPipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
    Import-SPTerms [-ParentTermStore] <SPTaxonomyTermStorePipeBind> [-InputFile] <XmlDocumentPipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
    Import-SPTerms [-ParentGroup] <SPTaxonomyGroupPipeBind> [-InputFile] <XmlDocumentPipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
    Import-SPTerms [-ParentTermSet] <SPTaxonomyTermSetPipeBind> [-InputFile] <XmlDocumentPipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
    Import-SPTerms [-ParentTerm] <SPTaxonomyTermPipeBind> [-InputFile] <XmlDocumentPipeBind> [-AssignmentCollection <SPAssignmentCollection>] [<CommonParameters>]
   
   
DESCRIPTION
    Import the Managed Metadata Terms.
   
    Copyright 2011 Falchion Consulting, LLC
    > For more information on this cmdlet and others:
    > http://blog.falchionconsulting.com/
    > Use of this cmdlet is at your own risk.
    > Gary Lapointe assumes no liability.
   

PARAMETERS
    -TaxonomySession <SPTaxonomySessionPipeBind>
        The TaxonomySession object to import Term Stores into.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
       
    -ParentTermStore <SPTaxonomyTermStorePipeBind>
        The TermStore object to import Groups into.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false
       
    -ParentGroup <SPTaxonomyGroupPipeBind>
        The Group object to import Term Sets into.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false
       
    -ParentTermSet <SPTaxonomyTermSetPipeBind>
        The TermSet object to import Terms into.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false
       
    -ParentTerm <SPTaxonomyTermPipeBind>
        The Term object to import Terms into.
       
        Required?                    true
        Position?                    1
        Default value               
        Accept pipeline input?       true (ByValue, ByPropertyName)
        Accept wildcard characters?  false
       
    -InputFile <XmlDocumentPipeBind>
        The path to the file containing the terms to import or an XmlDocument object or XML string.
       
        Required?                    true
        Position?                    2
        Default value               
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
       
    -AssignmentCollection [<SPAssignmentCollection>]
        Manages objects for the purpose of proper disposal. Use of objects, such as SPWeb or SPSite, can use large amounts of memory and use of these objects in Windows PowerShell scripts requires proper memory management. Using the SPAssignment object, you can assign objects to a variable and dispose of the objects after they are needed to free up memory. When SPWeb, SPSite, or SPSiteAdministration objects are used, the objects are automatically disposed of if an assignment collection or the Global parameter is not used.
       
        When the Global parameter is used, all objects are contained in the global store. If objects are not immediately used, or disposed of by using the Stop-SPAssignment command, an out-of-memory scenario can occur.
       
        Required?                    false
        Position?                    named
        Default value               
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
       
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer and OutVariable. For more information, type,
        "get-help about_commonparameters".
   
INPUTS
   
   
   
OUTPUTS
   
   
   
NOTES
   
   
    For more information, type "Get-Help Import-SPTerms -detailed". For technical information, type "Get-Help Import-SPTerms -full".
   
    ------------------EXAMPLE 1-----------------------
   
    PS C:\> Import-SPTerms -ParentTermStore (Get-SPTaxonomySession -Site "http://site/").TermStores[0] -InputFile "c:\terms.xml"
   
   
    This example imports the Group from c:\terms.xml to the first Term Store.
   
    ------------------EXAMPLE 2-----------------------
   
    PS C:\> Import-SPTerms -TaxonomySession "http://site/" -InputFile "c:\terms.xml"
   
   
    This example imports the terms from c:\terms.xml to the Term Store associated with http://site.
   
   
RELATED LINKS
    Export-SPTerms
    Get-SPTaxonomySession

 

Using these cmdlets I can easily export terms from one environment, such as production, to another environment, such as my test environment. This avoids the need to have backup and restore the database associated with the term store (which requires removing and re-provisioning the service application. And the way I’ve written the cmdlets all the Term Set, Group, and Term IDs remain consistent and, because the exported XML is easily modified, when it comes to Site Collection scoped Groups you can do a simple search and replace to change Site Collection URLs to match the new target (note that I first check for a Site Collection with the specified URL and if not found then I use the ID and if I still can’t locate the Site Collection then I create the Group as a standard shared Group).

-Gary

View the original post on Gary’s personal blog for comments and all related downloads: Exporting and Importing SharePoint 2010 Terms

11Mar/12

“Stamping” PDF Files Downloaded from SharePoint 2010

Posted by Gary Lapointe

First off I want to clarify that the subject of this post is not my idea as it is something that my friend Roman Kobzarev put together for his company and I merely assisted with getting the code to work. The problem that Roman was trying to solve was that his company provided numerous PDF files that registered/paying members could download and, unfortunately, they were finding some of those files being posted to various other sites without their permission; so in an attempt to at least discourage this they wanted to stamp the PDF files with some information about the user who downloaded the file.

There are various ways in which this problem can be solved but perhaps one of the simpler approaches, and the approach outlined here, was to create an HTTP Handler which intercepted requests for any PDF files and then simply retrieve the file from SharePoint, modify it, and then send it on its way. The cool thing about this approach and the pattern shown here is that it can easily be applied to any file type which requires some user or request specific modifications applied to it.

Before I get to the actual code I want to point out one third party dependency that we used: iTextSharp. This is an open source .NET library for PDF generation and manipulation. There are many options available when looking to manipulate PDF files and in this case this one was chosen due to its cost (free). So let’s get to the code.

In terms of code there really isn’t that much which is what makes this such a nice solution. The first piece is the actual implementation of the IHttpHandler interface:

using System.IO;
using System.Web;
using Microsoft.SharePoint;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace Aptillon.SharePoint.PDFWatermark
{
    public class PDFWatermarkHttpHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {

            SPFile file = SPContext.Current.Web.GetFile(context.Request.Url.ToString());
            byte[] content = file.OpenBinary();
            SPUser currentUser = SPContext.Current.Web.CurrentUser;

            string watermark = null;
            if (currentUser != null)
                watermark = "This download was specially prepared for " + currentUser.Name;

            if (watermark != null)
            {
                PdfReader pdfReader = new PdfReader(content);
                using (MemoryStream outputStream = new MemoryStream())
                using (PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream))
                {
                    for (int pageIndex = 1; pageIndex <= pdfReader.NumberOfPages; pageIndex++)
                    {
                        //Rectangle class in iText represent geometric representation... 
                        //in this case, rectangle object would contain page geometry
                        Rectangle pageRectangle = pdfReader.GetPageSizeWithRotation(pageIndex);
                        //PdfContentByte object contains graphics and text content of page returned by PdfStamper
                        PdfContentByte pdfData = pdfStamper.GetUnderContent(pageIndex);
                        //create font size for watermark
                        pdfData.SetFontAndSize(BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, BaseFont.NOT_EMBEDDED), 8);
                        //create new graphics state and assign opacity
                        PdfGState graphicsState = new PdfGState();
                        graphicsState.FillOpacity = 0.4F;
                        //set graphics state to PdfContentByte
                        pdfData.SetGState(graphicsState);
                        //indicates start of writing of text
                        pdfData.BeginText();
                        //show text as per position and rotation
                        pdfData.ShowTextAligned(Element.ALIGN_CENTER, watermark, pageRectangle.Width / 4, pageRectangle.Height / 44, 0);
                        //call endText to invalid font set
                        pdfData.EndText();
                    }
                    pdfStamper.Close();
                    content = outputStream.ToArray();
                }
            }
            context.Response.ContentType = "application/pdf";
            context.Response.BinaryWrite(content);
            context.Response.End();
        }

        public bool IsReusable
        {
            get { return false; }
        }
    }
}

 

As you can see from the previous code listing, the bulk of the code involves the actual processing of the PDF file but the core SharePoint specific piece is in the beginning of the ProcessRequest() method where we use the SPContext.Current.Web.GetFile() method to retrieve the actual file requested and then, if we can get an actual SPUser object, we create a simple message that will be added to the bottom of the PDF. I’m not going to cover what’s happening with the iTextSharp objects as the point of this article is to demonstrate the pattern which can easily be applied to other file types and not how to use iTextSharp.

To deploy this class I created an empty SharePoint 2010 project using Visual Studio 2010 and added the file to the project. I then created a new Web Application scoped Feature which I use to add the appropriate web.config settings which will register the HTTP Handler. The following screenshot shows the final project structure:

SNAGHTML8af78243

Note that I also added the iTextSharp.dll to the project and added it as an additional assembly to the package by double clicking the Package.package element and then, in the package designer, click Advanced to add additional assemblies:

image

Before I show the code for the Web Application Feature I first want to show what settings I set for the Feature after adding it:

image

When you add a new Feature to a project it’s going to name it Feature1 and set the default scope to Web. The first thing I do is rename the Feature to something meaningful – in this case, because I know I’ll only have the one Feature I go ahead and name it the same as the project name: Aptillon.SharePoint.PDFWatermark (I always follow the same naming convention for my projects, which equate to WSP file names and Features: <Company>.SharePoint.<Something Appropriate for the Contained Functionality>). The next thing I do is change the Deployment Path property for the Feature so that it only uses the Feature name and does not prepend the project name; and finally I set the scope and title of the Feature. Now I’m ready to add my Feature activation event receiver.

The code that I want to include in the event receiver will handle the addition and removal of the web.config handler elements. I do this using the SPWebConfigModification class. Now there’s debate on whether this should be used or not; this is one of those classes where you might say (as my friend Spence Harbar puts it), “Just because you should use it doesn’t mean you can.” The simple explanation for this is that ideally you should be using this class to make web.config modifications but the reality is that this guy is fraught with issues and usually doesn’t work. That said, what I usually do is, where it makes sense, use this class to add and remove my entries but work under the premise that it will probably not work and plan on making these changes manually (or write a timer job which will do what this guy is attempting to do, but that’s out of scope of this article). So here’s the FeatureActivated() and FeatureDeactivating() methods that I use to add and remove the appropriate web.config entries which register the previously defined HTTP Handler:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    string asmDetails = typeof(PDFWatermarkHttpHandler).AssemblyQualifiedName;

    SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
    if (webApp == null) return;

    SPWebConfigModification modification = new SPWebConfigModification("add[@name=\"PDFWatermark\"]", "configuration/system.webServer/handlers");
    modification.Value = string.Format("<add name=\"PDFWatermark\" verb=\"*\" path=\"*.pdf\" type=\"{0}\" preCondition=\"integratedMode\" />", asmDetails);

    modification.Sequence = 1;
    modification.Owner = asmDetails;
    modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
    webApp.WebConfigModifications.Add(modification);

    webApp.Update();
    webApp.WebService.ApplyWebConfigModifications();
}


public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    string asmDetails = typeof(PDFWatermarkHttpHandler).AssemblyQualifiedName;

    SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
    if (webApp == null) return;

    List<SPWebConfigModification> configModsFound = new List<SPWebConfigModification>();
    Collection<SPWebConfigModification> modsCollection = webApp.WebConfigModifications;
    for (int i = 0; i < modsCollection.Count; i++)
    {
        if (modsCollection[i].Owner == asmDetails)
        {
            configModsFound.Add(modsCollection[i]);
        }
    }

    if (configModsFound.Count > 0)
    {
        foreach (SPWebConfigModification mod in configModsFound)
            modsCollection.Remove(mod);

        webApp.Update();
        webApp.WebService.ApplyWebConfigModifications();
    }
}

 

With all the code in place I can deploy to my Web Application and now any time a PDF is downloaded I’ll have a nice little message displayed on the bottom of each page. Again, the intent here is to show the simplicity of the pattern and approach – with a little imagination you can easily come up with lots of other uses for this (applying security or password protection to the PDF, adding an image watermark, removing pages based on registration status thus providing “sample” versions, prefilling form fields with user data, adding a version history page, etc.). And of course all this can also be applied to other file types such as the Office files or images (though image handling would take a little more logic to ignore images not coming from document libraries).

View the original post on Gary’s personal blog for comments and all related downloads: “Stamping” PDF Files Downloaded from SharePoint 2010