Saturday 29 November 2014

Exporting UPA profile connection mappings from SharePoint

The below script allows you to export all the profile properties used in the User Profile Service along with any mapping used for a particular Profile Sync connection.

Replace the bits in yellow with your own variables:

Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue

#Get the site context
$siteUrl = "<<Site Url>>"
$site = Get-SPSite $siteUrl
$context = Get-SPServiceContext $site


#Retrieve a list of all property mappings
function Get-Mappings ($context)
{
    $upam = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($context)
    $cm = $upam.ConnectionManager["<<Name of your UPA sync connection>>"]
    $maps = $cm.PropertyMapping.GetEnumerator()
   
    $maps | ForEach-Object{
      $returnValue = @{
        "Column" = $_.ProfileProperty.Name
        "Mapping" = $_.DataSourcePropertyName
      }
     
      New-Object PSObject -Property $returnValue | Select ("Column", "Mapping")
    }
}

#Create a new UserProfileConfigManager object
$upam = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($context)

#Get the core properties
$coreProps = $upam.ProfilePropertyManager.GetCoreProperties()


#Match the core properties to their coresponding mappings
function Get-CorePropsMapping ($coreProps, $maps)
{
    foreach ($coreProp in $coreProps)
    {
        $map = $maps | Where-Object {$_.Column -eq $coreProp.Name}
     
        $value = @{
            "Column" = $coreProp.DisplayName
            "Mapping" = $map.Mapping
        }
     
       New-Object PSObject -Property  $value | Select ("Column", "Mapping")
    }
}

#Call the get mappings function
$maps = Get-Mappings -context $context

#Call the CorePropsMapping function and output to a gridview
Get-CorePropsMapping $coreProps $maps | Out-GridView

This can be used if you should need to recreate the mapping or UPA service (...say if the User Profile Sync service is stuck starting.. for instance)

Thursday 12 September 2013

InfoPath: Submitting a form to 'this' folder within a document library

Commonly users want to create and submit a InfoPath form in the same location as they opened it regardless whether it is the root of a document library or within a folder or document set.

Obviously the save button is available, however they will have to navigate to the correct folder or there may be naming conventions which a submit command can satisfy.

Without code you can submit all forms to the same library using a SharePoint connection, in order to save to a specific library or folder you need to use custom code. Within a form to be used in form filler you can simply use this.save or this.saveas, however within Form Services (web forms) Form the Microsoft.Office.InfoPath namespace / binary is only a sub set of the form filler binary, and neither save or saveas are methods in this namespace.

The below code works for both new and opened forms. In order to work you need to create a SharePoint submit datasource within the InfoPath form (in this case called submit).

using Microsoft.Office.InfoPath;
using System;
using System.Xml;
using System.Xml.XPath;

namespace BrowserTest
{
public partial class FormCode
{
//Is an object used when the form is opened to store the Url the form was openned from
private object _uri
{
get
{
return FormState["_uri"];
}
set
{
FormState[
"_uri"] = value;
}
}

public void InternalStartup()
{
EventManager.FormEvents.Loading +=
new LoadingEventHandler(FormEvents_Loading);
EventManager.FormEvents.Submit +=
new SubmitEventHandler(FormEvents_Submit);
}

public void FormEvents_Loading(object sender, LoadingEventArgs e)
{
try
{
//Input parameters are query string variables
//Save location is the variable name when the form is first openned
this._uri = e.InputParameters["SaveLocation"].ToString();
}
catch
{
//If the save location is not present the exception will be caught
//The XMLLocation will be used instead
string XMLLoc = e.InputParameters["XmlLocation"].ToString();
//The XMLLocation is relative so there the site collection url is appended to it
this._uri = this.ServerInfo.SharePointServerRootUrl + XMLLoc.Substring(1, XMLLoc.LastIndexOf("/"));
}
}

public void FormEvents_Submit(object sender, SubmitEventArgs e)
{
//When the form is submitted
try
{
//Get the SharePoint submit connection (as previous described)
FileSubmitConnection dc = (FileSubmitConnection)this.DataConnections["Submit"];
if (dc != null)
{
//Sets the folder url of the submit method then executes
dc.FolderUrl = this._uri;
dc.Execute();
e.CancelableArgs.Cancel =
false;
}
}
catch
{
//Cancels the submit if an exception is thrown
e.CancelableArgs.Cancel = true;
}

}
}
}







This code can be added to a form which is published as an administrator approved form (with full control) and then published through form services.

Monday 21 January 2013

Using Powershell to import a Managed Metadata termset from XML file

This post follows on from Part 1

Importing a termset from XML file 

Our XML file will have the following structure:
   1: <xml version="1.0">

   2: <TermStore Name="ABC" GUID="xxxx-xxxx-xxxx-xxxx">

   3:     <TermSetGroup Name="DEF" GUID="xxxx-xxxx-xxxx-xxxx">

   4:         <TermSet Name="GHI" GUID="xxxx-xxxx-xxxx-xxxx">

   5:             <Term>

   6:             <GUID>xxxx-xxxx-xxxx-xxxx</GUID>

   7:             <Name>JKL</Name>

   8:             <Description>blah</Description>

   9:             <IsAvailableForTagging>False</IsAvailableForTagging>

  10:                 <ChildTerms>

  11:                     <ChildTerm>

  12:                     <GUID>xxxx-xxxx-xxxx-xxxx</GUID>

  13:                     <Name>JKL Child</Name>

  14:                     <Description>blah</Description>

  15:                     <IsAvailableForTagging>True</IsAvailableForTagging>

  16:                     </ChildTerm>

  17:                 </ChildTerms>

  18:             </Term>

  19:         </TermSet>

  20:     </TermSetGroup>

  21: </TermStore>



First of all add the SharePoint Shell into the script:




   1:  

   2: Add-PSSnapin “Microsoft.SharePoint.Powershell” –ErrorAction SilentlyContinue

   3:  

Then create a SharePoint Taxonomy session, and open the termstore on the target site collection :




   1: $siteUrl = “http://mySharePointSite/”

   2: $session = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($siteUrl)

   3: $Termstore = $session.TermStores[0]

Next open the XML file for reading using the Get-Content command

     


   1: $DocLoc =“C:\myXML.xml”

   2: [XML]$XMLTermset = get-content$DocLoc


Now you can loop round each of the nodes in the XML document, first of the groups (folder in termstore manager). Determine if it is present already in the termstore and add if necessary:



   1: foreach($XMLGroup in $XMLTermset.TermStore.TermSetGroup)

   2: {

   3:  #Loop round each TermSetGroup node in the XML Document

   4:  if ($TermStore.Groups[$XMLGroup.Name]–eq $null)

   5:  #Test to see if the group exists in the termstore

   6:    {

   7:     $Group = $TermStore.CreateGroup($XMLGroup.Name)

   8:     #If the group doesn’t exist create it using the creategroup method 

   9:    }

  10:  else 

  11:    {

  12:     $Group = $TermStore.Groups[$XMLGroup.Name)

  13:     #Else set the variable group

  14:    }

Whilst in the group loop we can setup the termsets within that group. Again test to see if the termset exists and create it if not.


N.B. The method for creating a termset is similar to that of a group however this time we also specify the GUID for the termset. The reason for this is, if you create a Visual Studio SharePoint package with a Managed Metadata column (either by hand writing it or extracting from a WSP) part of the definition is the termset GUID, ergo when you create the termset it needs to have the same GUID as in the SharePoint solution.  



   1: foreach ($XMLTermset in $XMLGroup.Termset)

   2: {

   3:     if ($Group.Termset[$XMLTermset.GUID] -eq $null)

   4:     #Test to see if the termset exists

   5:     {

   6:         $TermsetGUID = [guid]$XMLTermset.GUID

   7:         $Termset =  $Group.CreateTermSet($XMLTermset.Name, $TermsetGUID)

   8: `       #If the termset does not exist create a GUID object with the GUID in the xml node then create the termset using the createTermSet method, passing the GUID and the name of the termset

   9:     }

  10:     else 

  11:     {

  12:         $TermSet = $Group.TermSets[$XMLTermset.GUID]

  13:         #If the termset already exists set the variable termset

  14:     }





Whilst in the termsets loop we can setup the Terms within that termset. Again test to see if the term exists and create it if not.


N.B. Again we specify the new items (in this case the term) GUID. This is for the same reason as above and also items which use the Managed Metadata column only store the GUID of the term, therefore the GUID’s need to match. Other properties can be set at the same time, the example below sets whether the term is available for tagging.


   1: foreach ($XMLTerm in $XMLTermset.Term)

   2: {

   3:     if ($TermSet.Terms[$XMLTerm.GUID] -eq $null)

   4:     #Test to see if the term exists    

   5:     {

   6:         $TermGUID = [guid]$XMLTerm.GUID

   7:         $Term = $TermSet.CreateTerm($XMLTerm.Name, 1033,$TermGUID)

   8:         #Create term, specifying the Locale (1033)

   9:         $Term.SetDescription($XMLTerm.Description, 1033)

  10:         #Set the terms description using the locale         

  11:  

  12:         if($XMLTerm.IsAvailableForTagging -eq "False")

  13:         {

  14:             $Term.IsAvailableForTagging = $false

  15:             #If the term is not available for tagging

  16:         }

  17:     else

  18:     {

  19:         $Term = $Termset.Terms[$XMLTerm.GUID]

  20:         #If the term exists set the variable term

  21:     }

Do the same for child terms whilst in the terms loop.





   1: foreach ($XMLChildTerm in $XMLTerm.ChildTerms.ChildTerm)

   2: {

   3:     if($XMLTern.ChildTerms -ne $null)

   4:     {

   5:         if ($Term.Terms[$XMLChildTerm.GUID] -eq $null)

   6:         #Test to see if the term exists    

   7:         {

   8:             $ChildTermGUID = [guid]$XMLChildTerm.GUID

   9:             $ChildTerm = $Term.CreateTerm($XMLChildTerm.Name, 1033,$ChildTermGUID)

  10:             #Create term, specifying the Locale (1033)

  11:             $ChildTerm.SetDescription($XMLChildTerm.Description, 1033)

  12:             #Set the terms description using the locale         

  13:  

  14:             if($XMLChildTerm.IsAvailableForTagging -eq "False")

  15:             {

  16:                 $ChildTerm.IsAvailableForTagging = $false

  17:                 #If the term is not available for tagging

  18:             }

  19:         else

  20:         {

  21:             $ChildTerm = $Termset.Terms[$XMLTerm.GUID]

  22:             #If the term exists set the variable term

  23:         }

  24:     }


Next close all the loops and commit the changes to the termstore (if you do not do this no changes will be made).


   1: $TermStore.CommitAll();

Finally SharePoint stores a copy of the termstore in a hidden list called “TaxonomyHiddenList” in the site. In order to update the termstore you need to tell SharePoint to resync the hidden list:


   1: $session.DefaultKeywordsTermStore.ReSyncHiddenList();

However this may not always work, if you are restore a content database onto a site then the hidden list also comes across (as you would aspect). Take a look at the hidden list’s field (if needs be look in SharePoint designer):


  • Title

  • IdForTermStore

  • IdForTerm

  • IdForTermSet

  • Term

  • Path

  • CatchAllData

  • CatchAllDataLabel

  • Term1033 (or Locale)

  • Path1033

The offending item here is the IdForTermStore, this stores the Term Store ID which is unique to the farm and cannot be set through Powershell nor can it be omitted in the Visual Studio field definition. So what you need to do is write a piece of script to get all the items in this hidden list and update the IdForTermStore with the ID of the TermStore.Id, after resync the list and all items which use the term store should now have valid up to date content.

Thursday 17 January 2013

Extending a web application to allow anonymous access

Perform the following steps
  1. In Central Administration under Application Management go to “Manage web applications”.
  2. Next select the Web application you wish to extend.
  3. In the ribbon Web Applications > under Contribute click “Extend”
  4. In the resulting pop-up choose Create new IIS Website and change the Port number and Host header to suit
  5. Select NTLM as the Authentication provider and Allow anonymous (to yes), choose SSL as appropriate
  6. Under the zone select Internet
  7. Clicking ok will extended the web application to a new application pool. This may take a while.
  8. Next head to the site through the new port number and you will notice you are still authenticated (i.e. not anonymous). Go to All site settings and “Site permissions”.
  9. In the ribbon a new button will be visible with the title “Anonymous access”. Click that and select either:
    • Entire Site - will give View items across the entire site for anonymous users 
    • Lists and libraries – will allow anonymous users access to the lists and libraries which they are granted access to directly or indirectly (lists with permissions inheriting from parent). You can allow anonymous users permissions to update a list but not a document library, to do this navigate to the list and select permissions. Again select “Anonymous access” and select the rights they should have on the list.

Cannot delete folder in the 14 hive / Control templates folder

Sometime you may deploy a solution from Visual Studio and find that an error message come back similar to:
“Error occurred in deployment step ‘Add Solution’ Access to path <<Path>> is denied”
If you navigate to the folder and try and change it to not be read only, you get a permissions error. In order to fix this do an IIS stop (iisreset –stop in cmd prompt) deploy your solution and do an IIS Start (iisreset –start in cmd prompt). From now on deploying the solution should work fine.

Wednesday 16 January 2013

Error occurred in deployment step 'Add Solution': The solution cannot be deployed. The feature '<Feature>' uses the directory “<Folder>" in the solution.

Came across this problem the other day, it occurs when you retract and uninstall a SharePoint feature, then try and install the WSP again. The obvious thing to is check to see what the feature is in powershell using; get-spfeature <GUID> however this returns nothing.
The answer is to use the old trusty sledgehammer of stsadm, navigate to the 14 hive/bin folder and run the following command:
   1:  

   2: stsadm–o uninstallfeature–id <<GUID of feature>>–force

This should sort the feature and allow you to install again.

Tuesday 8 January 2013

Using Powershell to export a SharePoint Managed Metadata termset to xml


The problem

You have a termset in one environment and want to move the termset and the sites content to another environment.

SharePoint OTB does allow you to import a termset from a CSV file (which can be easily edited in Excel). However should you restore a content database; then import your termset using the OTB method none of the content items will have valid entries.

The reason for this is first managed metadata , data is sorted in a separate database away from the content database, secondly content items do not store the actual term; they store the terms GUID. When using to OTB method for importing the termset, SharePoint does not allow you to specify the GUID and creates a new GUID. The final part of this problem is the site collection itself has a copy of the termset, stored in a hidden list called  “TaxonomyHiddenList” should the managed metadata (MM) service go down. You need to tell SharePoint to update this list to get the latest terms.

The solution

One potential solution is to backup and restore the MM database, however this would change all termsets and you may wish to just import one. So the best solution I have come up with is to write a Powershell script which exports the termset to a XML file and a corresponding script to import it.

To Export

First of all add the SharePoint Shell into the script:

   1: Add-PSSnapin “Microsoft.SharePoint.Powershell” –ErrorAction SilentlyContinue




Then create a SharePoint Taxonomy session:




   1: $siteUrl = “http://mySharePointSite/”

   2: $session = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($siteUrl)




Next get the Termstore, Group and Termset, all whilst writing to a XML string variable:




   1: $termSetStoreDesc = “Managed Metadata Service”

   2: $groupDesc = “MyGroup”

   3: $termSetName = “MyTermSet”

   4:  

   5: $XML = “<?xml version=’1.0’ ?>

   6: $termStore = $session.TermStores[$termSetStoreDesc]

   7: $XML += “<TermStore Name=’” + $termStore.Name + “’ GUID=’” + $termStore.ID + “’>

   8:  

   9: $group = $termStore.Groups[$groupDesc] 

  10:  

  11: $XML += “<Group Name=’” + $group.Name + “’ GUID=’” + $group.ID + “’>

  12:  

  13: $termSet = $Group.TermSets[$termSetName]

  14:  

  15: $XML = “<TermSetGroup Name=’” + $termSet.Name + ‘” GUID-‘” + $termSet.ID + “’>




Then get the terms within that termset and loop round them writing to XML and get the term beneath them and do the same:




   1: $terms = $termSet.GetTerms(200) 

   2:  

   3: Foreach ($term in $terms)

   4: {

   5:  

   6: $XML += “<Term><Name>” + $term.Name + “</Name><GUID>” + $term.ID + “</GUID>

   7:  

   8: If ($term.TermsCount –gt 0)

   9: {

  10: $XML += <ChildTerms>

  11:  

  12:     Foreach ($childTerm in $Term.Terms)

  13:     {

  14:          XML += “<Term><Name>” + $childTerm.Name + “</Name><GUID>” + $childTerm.ID + “</GUID></Term>

  15:     }

  16:  $XML += </ChildTerms>

  17: }

  18: $XML += </Term>


       }



Finally close the open XML tags and save the file:



      $


   1: $XML += “</TermSet></TermSetGroup></TermStore>

   2: $saveLoc = “C:\myXML.xml”

   3: $XML | out-File –FilePath $saveLoc


Altogether (properly structured)





   1: Add-PSSnapin “Microsoft.SharePoint.Powershell” –ErrorAction 

   2: SilentlyContinue

   3:  

   4: #Variables start

   5: $siteUrl = “http://mySharePointSite/”

   6: $termSetStoreDesc = “Managed Metadata Service”

   7:  

   8: $groupDesc = “MyGroup”

   9:  

  10: $termSetName = “MyTermSet”

  11: $saveLoc = “C:\myXML.xml”

   2: $session = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($siteUrl)

  13: #Variables end

  14:  

  15:  

  16:  $XML = “<?xml version=’1.0’?>

  17:  

  18: $termStore = $session.TermStores[$termSetStoreDesc]

  19:  

  20: $XML += “<TermStore Name=’” + $termStore.Name + “’ GUID=’” + $termStore.ID + “’>

  21:  

  22: $group = $termStore.Groups[$groupDesc] 

  23:  

  24: $XML += “<Group Name=’” + $group.Name + “’ GUID=’” + $group.ID + “’>

  25:  

  26: $termSet = $group.TermSets[$termSetName]

  27:  

  28: $XML = “<TermSetGroup Name=’” + $termSet.Name + ‘” GUID-‘” + $termSet.ID + ’>

  29:  

  30: $terms = $termSet.GetTerms(200) 

  31:  

  32: Foreach ($term in $terms)

  33: {

  34: $XML += “<Term><Name>” + $term.Name + “</Name><GUID>” + $term.ID + “</GUID>

  35:  

  36:     If ($term.TermsCount –gt 0)

  37:     {

  38:     $XML += "<ChildTerms>

  39:         Foreach ($childTerm in $term.Terms)

  40:         {

  41:             XML += “<Term><Name>” + $childTerm.Name + “</Name><GUID>” + $childTerm.ID + “</GUID></Term>

  42:         }

  43:         $XML += </ChildTerms>

  44:     }

  45:     $XML += </Term>

  46:     }

  47:      

  48:     $XML += “</TermSet></TermSetGroup></TermStore>

  49:  

  50: $XML | out-File –FilePath $saveLoc




If all done correctly your  output XML will have the following structure:

   1: <xml version="1.0">

   2: <TermStore Name="ABC" GUID="xxxx-xxxx-xxxx-xxxx">

   3:     <TermSetGroup Name="DEF" GUID="xxxx-xxxx-xxxx-xxxx">

   4:         <TermSet Name="GHI" GUID="xxxx-xxxx-xxxx-xxxx">

   5:             <Term>

   6:             <GUID>xxxx-xxxx-xxxx-xxxx</GUID>

   7:             <Name>JKL</Name>

   8:             <Description>blah</Description>

   9:             <IsAvailableForTagging>False</IsAvailableForTagging>

  10:                 <ChildTerms>

  11:                     <ChildTerm>

  12:                     <GUID>xxxx-xxxx-xxxx-xxxx</GUID>

  13:                     <Name>JKL Child</Name>

  14:                     <Description>blah</Description>

  15:                     <IsAvailableForTagging>True</IsAvailableForTagging>

  16:                     </ChildTerm>

  17:                 </ChildTerms>

  18:             </Term>

  19:         </TermSet>

  20:     </TermSetGroup>

  21: </TermStore>


To import this term set see this