// Copyright (c) 2012, Outercurve Foundation.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// - Neither the name of the Outercurve Foundation nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Security.Principal;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using WebsitePanel.Providers.SharePoint;
using WebsitePanel.Providers.Utils;
namespace WebsitePanel.Providers.HostedSolution
{
public class HostedSharePointServer2013Impl : MarshalByRefObject
{
#region Fields
private static RunspaceConfiguration runspaceConfiguration;
#endregion
#region Properties
private string SharepointSnapInName
{
get { return "Microsoft.SharePoint.Powershell"; }
}
#endregion
#region Methods
/// Gets list of SharePoint collections within root web application.
/// The root web application Uri.
/// List of SharePoint collections within root web application.
public SharePointSiteCollection[] GetSiteCollections(Uri rootWebApplicationUri)
{
return GetSPSiteCollections(rootWebApplicationUri).Select(pair => NewSiteCollection(pair.Value)).ToArray();
}
/// Gets list of supported languages by this installation of SharePoint.
/// The root web application Uri.
/// List of supported languages
public int[] GetSupportedLanguages(Uri rootWebApplicationUri)
{
var languages = new List();
try
{
WindowsImpersonationContext wic = WindowsIdentity.GetCurrent().Impersonate();
try
{
languages.AddRange(from SPLanguage lang in SPRegionalSettings.GlobalInstalledLanguages select lang.LCID);
}
finally
{
wic.Undo();
}
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to create site collection.", ex);
}
return languages.ToArray();
}
/// Gets site collection size in bytes.
/// The root web application uri.
/// The site collection url.
/// Size in bytes.
public long GetSiteCollectionSize(Uri rootWebApplicationUri, string url)
{
Dictionary sizes = GetSitesCollectionSize(rootWebApplicationUri, new[] {url});
if (sizes.Count() == 1)
{
return sizes.First().Value;
}
throw new ApplicationException(string.Format("SiteCollection {0} does not exist", url));
}
/// Gets sites disk space.
/// The root web application uri.
/// The sites urls.
/// The disk space.
public SharePointSiteDiskSpace[] CalculateSiteCollectionDiskSpace(Uri rootWebApplicationUri, string[] urls)
{
return GetSitesCollectionSize(rootWebApplicationUri, urls).Select(pair => new SharePointSiteDiskSpace {Url = pair.Key, DiskSpace = (long) Math.Round(pair.Value/1024.0/1024.0)}).ToArray();
}
/// Calculates size of the required seti collections.
/// The root web application uri.
/// The sites urls.
/// Calculated sizes.
private Dictionary GetSitesCollectionSize(Uri rootWebApplicationUri, IEnumerable urls)
{
Runspace runspace = null;
var result = new Dictionary();
try
{
runspace = OpenRunspace();
foreach (string url in urls)
{
string siteCollectionUrl = String.Format("{0}:{1}", url, rootWebApplicationUri.Port);
var scripts = new List {string.Format("$site=Get-SPSite -Identity \"{0}\"", siteCollectionUrl), "$site.RecalculateStorageUsed()", "$site.Usage.Storage"};
Collection scriptResult = ExecuteShellCommand(runspace, scripts);
if (scriptResult != null && scriptResult.Any())
{
result.Add(url, Convert.ToInt64(scriptResult.First().BaseObject));
}
}
}
finally
{
CloseRunspace(runspace);
}
return result;
}
/// Sets people picker OU.
/// The site.
/// OU.
public void SetPeoplePickerOu(string site, string ou)
{
HostedSolutionLog.LogStart("SetPeoplePickerOu");
HostedSolutionLog.LogInfo(" Site: {0}", site);
HostedSolutionLog.LogInfo(" OU: {0}", ou);
Runspace runspace = null;
try
{
runspace = OpenRunspace();
var cmd = new Command("Set-SPSite");
cmd.Parameters.Add("Identity", site);
cmd.Parameters.Add("UserAccountDirectoryPath", ou);
ExecuteShellCommand(runspace, cmd);
}
finally
{
CloseRunspace(runspace);
}
HostedSolutionLog.LogEnd("SetPeoplePickerOu");
}
/// Gets SharePoint collection within root web application with given name.
/// Root web application uri.
/// Url that uniquely identifies site collection to be loaded.
/// SharePoint collection within root web application with given name.
public SharePointSiteCollection GetSiteCollection(Uri rootWebApplicationUri, string url)
{
return NewSiteCollection(GetSPSiteCollection(rootWebApplicationUri, url));
}
/// Deletes quota.
/// The quota name.
private static void DeleteQuotaTemplate(string name)
{
SPFarm farm = SPFarm.Local;
var webService = farm.Services.GetValue("");
SPQuotaTemplateCollection quotaColl = webService.QuotaTemplates;
quotaColl.Delete(name);
}
/// Updates site collection quota.
/// The root uri.
/// The site collection url.
/// The max storage.
/// The warning storage value.
public void UpdateQuotas(Uri root, string url, long maxStorage, long warningStorage)
{
if (maxStorage != -1)
{
maxStorage = maxStorage*1024*1024;
}
else
{
maxStorage = 0;
}
if (warningStorage != -1 && maxStorage != -1)
{
warningStorage = Math.Min(warningStorage, maxStorage)*1024*1024;
}
else
{
warningStorage = 0;
}
Runspace runspace = null;
try
{
runspace = OpenRunspace();
GrantAccess(runspace, root);
string siteCollectionUrl = String.Format("{0}:{1}", url, root.Port);
var command = new Command("Set-SPSite");
command.Parameters.Add("Identity", siteCollectionUrl);
command.Parameters.Add("MaxSize", maxStorage);
command.Parameters.Add("WarningSize", warningStorage);
ExecuteShellCommand(runspace, command);
}
finally
{
CloseRunspace(runspace);
}
}
/// Grants acces to current user.
/// The runspace.
/// The root web application uri.
private void GrantAccess(Runspace runspace, Uri rootWebApplicationUri)
{
ExecuteShellCommand(runspace, new List {string.Format("$webApp=Get-SPWebApplication {0}", rootWebApplicationUri.AbsoluteUri), string.Format("$webApp.GrantAccessToProcessIdentity(\"{0}\")", WindowsIdentity.GetCurrent().Name)});
}
/// Deletes site collection.
/// The runspace.
/// The site collection url.
/// True - if active directory accounts should be deleted.
private void DeleteSiteCollection(Runspace runspace, string url, bool deleteADAccounts)
{
var command = new Command("Remove-SPSite");
command.Parameters.Add("Identity", url);
command.Parameters.Add("DeleteADAccounts", deleteADAccounts);
ExecuteShellCommand(runspace, command);
}
/// Creates site collection within predefined root web application.
/// Root web application uri.
/// Information about site coolection to be created.
/// Is thrown in case requested operation fails for any reason.
public void CreateSiteCollection(Uri rootWebApplicationUri, SharePointSiteCollection siteCollection)
{
HostedSolutionLog.LogStart("CreateSiteCollection");
WindowsImpersonationContext wic = null;
Runspace runspace = null;
try
{
wic = WindowsIdentity.GetCurrent().Impersonate();
runspace = OpenRunspace();
CreateCollection(runspace, rootWebApplicationUri, siteCollection);
}
finally
{
CloseRunspace(runspace);
HostedSolutionLog.LogEnd("CreateSiteCollection");
if (wic != null)
{
wic.Undo();
}
}
}
/// Creates site collection within predefined root web application.
/// The runspace.
/// Root web application uri.
/// Information about site coolection to be created.
/// Is thrown in case requested operation fails for any reason.
private void CreateCollection(Runspace runspace, Uri rootWebApplicationUri, SharePointSiteCollection siteCollection)
{
string siteCollectionUrl = String.Format("{0}:{1}", siteCollection.Url, rootWebApplicationUri.Port);
HostedSolutionLog.DebugInfo("siteCollectionUrl: {0}", siteCollectionUrl);
try
{
SPWebApplication rootWebApplication = SPWebApplication.Lookup(rootWebApplicationUri);
rootWebApplication.Sites.Add(siteCollectionUrl, siteCollection.Title, siteCollection.Description, (uint) siteCollection.LocaleId, String.Empty, siteCollection.OwnerLogin, siteCollection.OwnerName, siteCollection.OwnerEmail, null, null, null, true);
rootWebApplication.Update();
}
catch (Exception)
{
DeleteSiteCollection(runspace, siteCollectionUrl, true);
throw;
}
try
{
GrantAccess(runspace, rootWebApplicationUri);
var command = new Command("Set-SPSite");
command.Parameters.Add("Identity", siteCollectionUrl);
if (siteCollection.MaxSiteStorage != -1)
{
command.Parameters.Add("MaxSize", siteCollection.MaxSiteStorage*1024*1024);
}
if (siteCollection.WarningStorage != -1 && siteCollection.MaxSiteStorage != -1)
{
command.Parameters.Add("WarningSize", Math.Min(siteCollection.WarningStorage, siteCollection.MaxSiteStorage)*1024*1024);
}
ExecuteShellCommand(runspace, command);
}
catch (Exception)
{
DeleteQuotaTemplate(siteCollection.Title);
DeleteSiteCollection(runspace, siteCollectionUrl, true);
throw;
}
AddHostsRecord(siteCollection);
}
/// Deletes site collection under given url.
/// Root web application uri.
/// The site collection to be deleted.
/// Is thrown in case requested operation fails for any reason.
public void DeleteSiteCollection(Uri rootWebApplicationUri, SharePointSiteCollection siteCollection)
{
HostedSolutionLog.LogStart("DeleteSiteCollection");
Runspace runspace = null;
try
{
string siteCollectionUrl = String.Format("{0}:{1}", siteCollection.Url, rootWebApplicationUri.Port);
HostedSolutionLog.DebugInfo("siteCollectionUrl: {0}", siteCollectionUrl);
runspace = OpenRunspace();
DeleteSiteCollection(runspace, siteCollectionUrl, false);
RemoveHostsRecord(siteCollection);
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to delete site collection.", ex);
}
finally
{
CloseRunspace(runspace);
HostedSolutionLog.LogEnd("DeleteSiteCollection");
}
}
/// Backups site collection under give url.
/// Root web application uri.
/// Url that uniquely identifies site collection to be deleted.
/// Resulting backup file name.
/// A value which shows whether created backup must be archived.
/// Custom temp path for backup
/// Full path to created backup.
/// Is thrown in case requested operation fails for any reason.
public string BackupSiteCollection(Uri rootWebApplicationUri, string url, string filename, bool zip, string tempPath)
{
try
{
string siteCollectionUrl = String.Format("{0}:{1}", url, rootWebApplicationUri.Port);
HostedSolutionLog.LogStart("BackupSiteCollection");
HostedSolutionLog.DebugInfo("siteCollectionUrl: {0}", siteCollectionUrl);
if (String.IsNullOrEmpty(tempPath))
{
tempPath = Path.GetTempPath();
}
string backupFileName = Path.Combine(tempPath, (zip ? StringUtils.CleanIdentifier(siteCollectionUrl) + ".bsh" : StringUtils.CleanIdentifier(filename)));
HostedSolutionLog.DebugInfo("backupFilePath: {0}", backupFileName);
Runspace runspace = null;
try
{
runspace = OpenRunspace();
var command = new Command("Backup-SPSite");
command.Parameters.Add("Identity", siteCollectionUrl);
command.Parameters.Add("Path", backupFileName);
ExecuteShellCommand(runspace, command);
if (zip)
{
string zipFile = Path.Combine(tempPath, filename);
string zipRoot = Path.GetDirectoryName(backupFileName);
FileUtils.ZipFiles(zipFile, zipRoot, new[] {Path.GetFileName(backupFileName)});
FileUtils.DeleteFile(backupFileName);
backupFileName = zipFile;
}
return backupFileName;
}
finally
{
CloseRunspace(runspace);
HostedSolutionLog.LogEnd("BackupSiteCollection");
}
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to backup site collection.", ex);
}
}
/// Restores site collection under given url from backup.
/// Root web application uri.
/// Site collection to be restored.
/// Backup file name to restore from.
/// Is thrown in case requested operation fails for any reason.
public void RestoreSiteCollection(Uri rootWebApplicationUri, SharePointSiteCollection siteCollection, string filename)
{
string url = siteCollection.Url;
try
{
string siteCollectionUrl = String.Format("{0}:{1}", url, rootWebApplicationUri.Port);
HostedSolutionLog.LogStart("RestoreSiteCollection");
HostedSolutionLog.DebugInfo("siteCollectionUrl: {0}", siteCollectionUrl);
HostedSolutionLog.DebugInfo("backupFilePath: {0}", filename);
Runspace runspace = null;
try
{
string tempPath = Path.GetTempPath();
string expandedFile = filename;
if (Path.GetExtension(filename).ToLower() == ".zip")
{
expandedFile = FileUtils.UnzipFiles(filename, tempPath)[0];
// Delete zip archive.
FileUtils.DeleteFile(filename);
}
runspace = OpenRunspace();
DeleteSiteCollection(runspace, siteCollectionUrl, false);
var command = new Command("Restore-SPSite");
command.Parameters.Add("Identity", siteCollectionUrl);
command.Parameters.Add("Path", filename);
ExecuteShellCommand(runspace, command);
command = new Command("Set-SPSite");
command.Parameters.Add("Identity", siteCollectionUrl);
command.Parameters.Add("OwnerAlias", siteCollection.OwnerLogin);
ExecuteShellCommand(runspace, command);
command = new Command("Set-SPUser");
command.Parameters.Add("Identity", siteCollection.OwnerLogin);
command.Parameters.Add("Email", siteCollection.OwnerEmail);
command.Parameters.Add("DisplayName", siteCollection.Name);
ExecuteShellCommand(runspace, command);
FileUtils.DeleteFile(expandedFile);
}
finally
{
CloseRunspace(runspace);
HostedSolutionLog.LogEnd("RestoreSiteCollection");
}
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to restore site collection.", ex);
}
}
/// Creates new site collection with information from administration object.
/// Administration object.
private static SharePointSiteCollection NewSiteCollection(SPSite site)
{
var siteUri = new Uri(site.Url);
string url = (siteUri.Port > 0) ? site.Url.Replace(String.Format(":{0}", siteUri.Port), String.Empty) : site.Url;
return new SharePointSiteCollection {Url = url, OwnerLogin = site.Owner.LoginName, OwnerName = site.Owner.Name, OwnerEmail = site.Owner.Email, LocaleId = site.RootWeb.Locale.LCID, Title = site.RootWeb.Title, Description = site.RootWeb.Description, Bandwidth = site.Usage.Bandwidth, Diskspace = site.Usage.Storage, MaxSiteStorage = site.Quota.StorageMaximumLevel, WarningStorage = site.Quota.StorageWarningLevel};
}
/// Gets SharePoint sites collection.
/// The root web application uri.
/// The SharePoint sites.
private Dictionary GetSPSiteCollections(Uri rootWebApplicationUri)
{
Runspace runspace = null;
var collections = new Dictionary();
try
{
runspace = OpenRunspace();
var cmd = new Command("Get-SPSite");
cmd.Parameters.Add("WebApplication", rootWebApplicationUri.AbsoluteUri);
Collection result = ExecuteShellCommand(runspace, cmd);
if (result != null)
{
foreach (PSObject psObject in result)
{
var spSite = psObject.BaseObject as SPSite;
if (spSite != null)
{
collections.Add(spSite.Url, spSite);
}
}
}
}
finally
{
CloseRunspace(runspace);
}
return collections;
}
/// Gets SharePoint site collection.
/// The root web application uri.
/// The required site url.
/// The SharePoint sites.
private SPSite GetSPSiteCollection(Uri rootWebApplicationUri, string url)
{
Runspace runspace = null;
try
{
string siteCollectionUrl = String.Format("{0}:{1}", url, rootWebApplicationUri.Port);
runspace = OpenRunspace();
var cmd = new Command("Get-SPSite");
cmd.Parameters.Add("Identity", siteCollectionUrl);
Collection result = ExecuteShellCommand(runspace, cmd);
if (result != null && result.Count() == 1)
{
var spSite = result.First().BaseObject as SPSite;
if (spSite == null)
{
throw new ApplicationException(string.Format("SiteCollection {0} does not exist", url));
}
return result.First().BaseObject as SPSite;
}
else
{
throw new ApplicationException(string.Format("SiteCollection {0} does not exist", url));
}
}
catch (Exception ex)
{
HostedSolutionLog.LogError(ex);
throw;
}
finally
{
CloseRunspace(runspace);
}
}
/// Opens PowerShell runspace.
/// The runspace.
private Runspace OpenRunspace()
{
HostedSolutionLog.LogStart("OpenRunspace");
if (runspaceConfiguration == null)
{
runspaceConfiguration = RunspaceConfiguration.Create();
PSSnapInException exception;
runspaceConfiguration.AddPSSnapIn(SharepointSnapInName, out exception);
HostedSolutionLog.LogInfo("Sharepoint snapin loaded");
if (exception != null)
{
HostedSolutionLog.LogWarning("SnapIn error", exception);
}
}
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
runspace.SessionStateProxy.SetVariable("ConfirmPreference", "none");
HostedSolutionLog.LogEnd("OpenRunspace");
return runspace;
}
/// Closes runspace.
/// The runspace.
private void CloseRunspace(Runspace runspace)
{
try
{
if (runspace != null && runspace.RunspaceStateInfo.State == RunspaceState.Opened)
{
runspace.Close();
}
}
catch (Exception ex)
{
HostedSolutionLog.LogError("Runspace error", ex);
}
}
/// Executes shell command.
/// The runspace.
/// The command to be executed.
/// PSobjecs collection.
private Collection ExecuteShellCommand(Runspace runspace, object cmd)
{
object[] errors;
var command = cmd as Command;
if (command != null)
{
return ExecuteShellCommand(runspace, command, out errors);
}
return ExecuteShellCommand(runspace, cmd as List, out errors);
}
/// Executes shell command.
/// The runspace.
/// The command to be executed.
/// The errors.
/// PSobjecs collection.
private Collection ExecuteShellCommand(Runspace runspace, Command cmd, out object[] errors)
{
HostedSolutionLog.LogStart("ExecuteShellCommand");
var errorList = new List