Enabling Anonymous SharePoint 2013 REST Search by Entering One URL in PowerShell
Anonymous REST Search with PowerShell

Enabling Anonymous SharePoint 2013 REST Search by Entering One URL in PowerShell

Posted by on Wednesday, June 12th, 2013  

 

Recently we have needed to configure Anonymous Search for SharePoint 2013 via the REST API. The esoteric alchemical formulae that are required to make this work are documented wonderfully by Waldek Mastykarz and can be found here: http://blog.mastykarz.nl/configuring-sharepoint-2013-search-rest-api-anonymous-users/

The punch line of Waldek ‘s article is that you must create a very particularly named library, with an exactly named XML file, with just the right contents in the root site of your site collection. If you have not done so you will get the following error message.
The SafeQueryPropertiesTemplateUrl “The SafeQueryPropertiesTemplateUrl "{0}" is not a valid URL.” is not a valid URL.
Overcoming the issue is easy enough, particularly if you have the blog post above, but it is tedious if you must repeat the process over and over and over again.

My thought was why not do this all with PowerShell? We need three, easy to obtain, pieces of information:
1) The Farm’s GUID
2) The Appropriate Site Collection’s GUID
3) The GUID of the Top Level Site (RootWeb) of the Site Collection in #2

These three values must be inserted into an XML file named QueryParameterTemplate.XML and that file must be contained in a document library, in the Top Level Site, named QueryPropertiesTemplate.

Also, I wanted this to be a single file — Grab it and Go Solution. Therefore, I embedded the necessary XML IN the PowerShell script. It is a comment block at the end of the file. That means this file can be kept somewhere handy and run at a moment’s notice. No ZIP files or keeping multiple files together. The script takes a URL as the only parameter and will prompt for the value if it is not supplied when the script isit run from the command line.

To use the script, simply type:

.\EnableAnonymousRESTSearch.ps1

The script automatically finds the appropriate IDs, if necessary creates a QueryPropertiesTemplate document library and uploads the QueryParameterTemplate.XML having inserted the appropriate IDs.
For those who are interested, I will give a short run down of the blocks of the script and what they do.

Param(
[string] $url = $(Read-Host-prompt "Enter Site Collection URL: ")
)
if ( (Get-PSSnapin-Name Microsoft.SharePoint.PowerShell
-ErrorAction SilentlyContinue) -eq $null )
{
    Add-PsSnapin Microsoft.SharePoint.PowerShell
}

This does two things. First we get an input parameter of the URL of the target Site Collection. As mentioned, this can be entered at the command line or if it is forgotten PowerShell will prompt for the URL with the message, “Enter Site Collection URL.” Secondly, the code block ensures that Microsoft.SharePoint.PowerShell is referenced ensuring we have PowerShell access to the SharePoint object model.

$farmID=(Get-SPFarm).Id
$spSite=Get-SPSite $url
$siteColId=$spSite.Id
$topLevelSiteID=$spSite.RootWeb.Id
$rootWeb=$spSite.RootWeb

In the next section, we retrieve some needed information, the Farm ID, the Site Collection ID, and the Top Level Site’s ID. These will all need to be inserted into the XML file that we create and upload to SharePoint. The $rootWeb variable will be used to find and/or create the necessary list and it is also important in that we only proceed if $rootWeb is not null.

if($null -ne $rootWeb)

Doing so prevents a number of extra error messages from being displayed if something went wrong.

$list=$rootWeb.Lists["QueryPropertiesTemplate"]
           if($null -eq $list)
           {
                $template=$rootWeb.ListTemplates["Document Library"]
                $list=$rootWeb.Lists[$rootWeb.Lists.Add("QueryPropertiesTemplate",
                                                        "QueryPropertiesTemplate",$template)]
           }

In this section we first attempt to find the list named QueryPropertiesTemplate. If it is not present ($null –eq $list) we must create a new document library.

$rootWeb.Lists.Add("QueryPropertiesTemplate","QueryPropertiesTemplate",$template)

Lists.Add() adds a new list or library to a SharePoint. (SharePoint Document Libraries SPDocumentLibrary are based on, that is inherit from, SharePoint Lists SPList.) The method above takes three parameters a Name, Description, and Template. The template has been retrieved from the list of available List Templates using:  $template=$rootWeb.ListTemplates[“Document Library”]

$scriptFile = $MyInvocation.MyCommand.Definition
     [string]$currentSource = Get-Content $scriptFile
     [int]$startScript=$currentSource.LastIndexOf("<QueryPropertiesTemplate");
     [int]$closeComment=$currentSource.LastIndexOf("#>");
     $xmlFile=($currentSource.Substring($startScript,$closeComment-$startScript))
     $xmlFile.QueryPropertiesTemplate.QueryProperties.FarmId=$farmID.ToString()
     $xmlFile.QueryPropertiesTemplate.QueryProperties.SiteId=$siteColId.ToString()
     $xmlFile.QueryPropertiesTemplate.QueryProperties.WebId=$topLevelSiteID.ToString()

Now we have reached point where we need to start processing the XML contained at the end of the file. To do so we need to find the script file that is currently executing. We do this with $scriptFile = $MyInvocation.MyCommand.Definition which gives the path of our script which is currently running. Using the path [string]$currentSource = Get-Content $scriptFile we can open and read the contents of the script file using Get-Content $scriptFile. This provides us with a string of scripts source. To find the XML at the end of the file, we 2 numbers the last index of the string ”<QueryPropertiesTemplate” and the last index of the close , “”#>”, of the PowerShell block quote, “”#>”. . Note that we must use the LastIndexOf for the QueryPropertiesTemplate string. Why? Because the first index of the string in the script’s source code is in the line:
[int]$startScript=$currentSource.LastIndexOf(“<QueryPropertiesTemplate”);
This is the line we are currently executing!$scriptFile = $MyInvocation.MyCommand.Definition

       [string]$currentSource = Get-Content $scriptFile
       [int]$startScript=$currentSource.LastIndexOf("<QueryPropertiesTemplate");
       [int]$closeComment=$currentSource.LastIndexOf("#>");
       $xmlFile=($currentSource.Substring($startScript,$closeComment-$startScript))
...
       Write-Host " "
       if($null -ne $file)
       {
              Write-Host ("File Created At " + $rootWeb.Url + "/" + $file.Url)
       }
}
<#
<QueryPropertiesTemplate xmlns="http://www.microsoft.com/sharepoint/search/KnownTypes/2008/08" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <QueryProperties i:type="KeywordQueryProperties">
    <EnableStemming>true</EnableStemming>
    <FarmId>22222222-fa49-4987-b1ea-9fad99e81a0f</FarmId>
    <SiteId>11111111-68f0-41c5-a525-7e7fda7666b3</SiteId>
    <WebId>00000000-8d97-40a2-b07b-cf05e4c85084</WebId>

 

After finding the text of the XML contained in our script file we must now convert the string to XML.

$xmlFile=($currentSource.Substring($startScript,$closeComment-$startScript))

This starts the substring of our entire script file at the beginning of the <QueryPropertiesTemplate> tag and reads through the close of the last PowerShell block quote. The use of in, “$xmlFile=($currentSource…)” causes the variable $xmlFile to be a .NET System.Xml.XmlDocument.

This makes it easy to set our values using:

$xmlFile.QueryPropertiesTemplate.QueryProperties.FarmId=$farmID.ToString()
 $xmlFile.QueryPropertiesTemplate.QueryProperties.SiteId=$siteColId.ToString()
 $xmlFile.QueryPropertiesTemplate.QueryProperties.WebId=$topLevelSiteID.ToString()

 

PowerShell Statement XPATH Equivalent
QueryPropertiesTemplate.QueryProperties.FarmId /QueryPropertiesTemplate/QueryProperties/FarmId
QueryPropertiesTemplate.QueryProperties.SiteId /QueryPropertiesTemplate/QueryProperties/SiteId
QueryPropertiesTemplate.QueryProperties.WebId /QueryPropertiesTemplate/QueryProperties/WebId

 

Once we have the XML updated to the proper values we create a temporary file to upload. This gives us a file from which it is easy to get a stream object and it leaves a record of output of the script’s execution.

$xmlFile.OuterXml | Out-File queryparametertemplate.xml
$tempFile=Get-Item-LiteralPath "queryparametertemplate.xml"

The first line pipes the XML in xmlFile to a file on the file system named QueryParameterTemplate.xml. While the second line gets a reference to the temporary file on the file system so we can upload it.

$folder = $list.RootFolder
$stream=$tempFile.OpenRead()
$file = $folder.Files.Add($folder.Url+"/queryparametertemplate.xml",$stream,$true, "created by script",$false)
$stream.Close()

All that is left now is to actually upload the XML file. To do so we get a reference to the library’sies root folder with $list.RootFolder and we get a StreamReader from our temporary file with $tempFile.OpenRead(). These can be passed into $folder.Files.Add() which will either create a new document in SharePoint or over write an existing document with the same name.
(See http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfolder.files.aspx and http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfilecollection_methods.aspx)
Finally, if our file add was successful then the variable $file should not be null. We do a quick check, and if it is not null, we display a message indicating success which includes the URL of our XML configuration file.

Write-Host " "
     if($null -ne $file)
     {
           Write-Host ("File Created At " + $rootWeb.Url + "/" + $file.Url)
     }

The entire PowerShell script can be downloaded here: http://summit7systems.com/downloads/AnonymousRESTSearch.ps1

I would also like to thank Mark Rackley and Jason Mills for help with work that led to this blog post.

Cheers,
James

 

Disclaimer
The sample scripts are not supported under any Summit 7 Systems standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Summit 7 Systems further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Summit 7 Systems, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Summit 7 Systems has been advised of the possibility of such damages.

Posted by on Wednesday, June 12th, 2013  

Subscribe to RSS Feed

Sign Up for Newsletter

1

1

comments

Leave a Reply