// 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.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Text; using Microsoft.Win32; namespace WebsitePanel.Providers.HostedSolution { public class LyncBase : HostingServiceProviderBase, ILyncServer { #region Fields private static InitialSessionState session; #endregion #region Properties internal static string LyncRegistryPath { get; set; } internal static string LyncVersion { get; set; } internal string PoolFQDN { get { return ProviderSettings[LyncConstants.PoolFQDN]; } } internal string SimpleUrlRoot { get { return ProviderSettings[LyncConstants.SimpleUrlRoot]; } } internal string PrimaryDomainController { get { return ProviderSettings["PrimaryDomainController"]; } } internal string RootOU { get { return ProviderSettings["RootOU"]; } } internal string RootDomain { get { return ServerSettings.ADRootDomain; } } #endregion #region Methods public string CreateOrganization(string organizationId, string sipDomain, bool enableConferencing, bool enableConferencingVideo, int maxConferenceSize, bool enabledFederation, bool enabledEnterpriseVoice) { return CreateOrganizationInternal(organizationId, sipDomain, enableConferencingVideo, maxConferenceSize, enabledFederation, enabledEnterpriseVoice); } public virtual string GetOrganizationTenantId(string organizationId) { return "NoHostingPack"; } public virtual bool DeleteOrganization(string organizationId, string sipDomain) { return DeleteOrganizationInternal(organizationId, sipDomain); } public virtual bool CreateUser(string organizationId, string userUpn, LyncUserPlan plan) { return CreateUserInternal(organizationId, userUpn, plan); } public virtual LyncUser GetLyncUserGeneralSettings(string organizationId, string userUpn) { return GetLyncUserGeneralSettingsInternal(organizationId, userUpn); } public virtual bool SetLyncUserGeneralSettings(string organizationId, string userUpn, LyncUser lyncUser) { return SetLyncUserGeneralSettingsInternal(organizationId, userUpn, lyncUser); } public virtual bool SetLyncUserPlan(string organizationId, string userUpn, LyncUserPlan plan) { return SetLyncUserPlanInternal(organizationId, userUpn, plan, null); } public virtual bool DeleteUser(string userUpn) { return DeleteUserInternal(userUpn); } public virtual LyncFederationDomain[] GetFederationDomains(string organizationId) { return GetFederationDomainsInternal(organizationId); } public virtual bool AddFederationDomain(string organizationId, string domainName, string proxyFqdn) { return AddFederationDomainInternal(organizationId, domainName, proxyFqdn); } public virtual bool RemoveFederationDomain(string organizationId, string domainName) { return RemoveFederationDomainInternal(organizationId, domainName); } public virtual void ReloadConfiguration() { ReloadConfigurationInternal(); } public virtual string[] GetPolicyList(LyncPolicyType type, string name) { return GetPolicyListInternal(type, name); } public override bool IsInstalled() { bool bResult = false; RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(LyncRegistryPath); if (registryKey != null) { var value = (string) registryKey.GetValue("ProductVersion", null); if (value.StartsWith(LyncVersion)) { bResult = true; } registryKey.Close(); } return bResult; } internal virtual string CreateOrganizationInternal(string organizationId, string sipDomain, bool enableConferencingVideo, int maxConferenceSize, bool enabledFederation, bool enabledEnterpriseVoice) { throw new NotImplementedException(); } internal virtual bool DeleteOrganizationInternal(string organizationId, string sipDomain) { throw new NotImplementedException(); } internal virtual bool CreateUserInternal(string organizationId, string userUpn, LyncUserPlan plan) { throw new NotImplementedException(); } internal virtual LyncUser GetLyncUserGeneralSettingsInternal(string organizationId, string userUpn) { throw new NotImplementedException(); } internal virtual bool SetLyncUserGeneralSettingsInternal(string organizationId, string userUpn, LyncUser lyncUser) { throw new NotImplementedException(); } internal virtual bool SetLyncUserPlanInternal(string organizationId, string userUpn, LyncUserPlan plan, Runspace runspace) { throw new NotImplementedException(); } internal virtual bool DeleteUserInternal(string userUpn) { throw new NotImplementedException(); } internal virtual LyncFederationDomain[] GetFederationDomainsInternal(string organizationId) { throw new NotImplementedException(); } internal virtual bool AddFederationDomainInternal(string organizationId, string domainName, string proxyFqdn) { throw new NotImplementedException(); } internal virtual bool RemoveFederationDomainInternal(string organizationId, string domainName) { throw new NotImplementedException(); } internal virtual void ReloadConfigurationInternal() { throw new NotImplementedException(); } internal virtual string[] GetPolicyListInternal(LyncPolicyType type, string name) { throw new NotImplementedException(); } #region PowerShell integration /// Opens runspace. /// The runspace. internal Runspace OpenRunspace() { HostedSolutionLog.LogStart("OpenRunspace"); if (session == null) { session = InitialSessionState.CreateDefault(); session.ImportPSModule(new[] {"ActiveDirectory", "Lync"}); } Runspace runspace = RunspaceFactory.CreateRunspace(session); runspace.Open(); runspace.SessionStateProxy.SetVariable("ConfirmPreference", "none"); HostedSolutionLog.LogEnd("OpenRunspace"); return runspace; } /// Closes runspace. /// The runspace. internal 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. /// Scripts list. /// The result. internal Collection ExecuteShellCommand(Runspace runspace, List scripts) { object[] errors; return ExecuteShellCommand(runspace, scripts, out errors); } /// Executes shell command. /// The runspace. /// The command. /// True - if domain controller should be used. /// The result. internal Collection ExecuteShellCommand(Runspace runspace, Command command, bool useDomainController) { object[] errors; return ExecuteShellCommand(runspace, command, useDomainController, out errors); } /// Executes shell command. /// The runspace. /// The command. /// Errors list. /// The result. internal Collection ExecuteShellCommand(Runspace runspace, Command command, out object[] errors) { return ExecuteShellCommand(runspace, command, true, out errors); } /// Executes shell command. /// The runspace. /// The command. /// True - if domain controller should be used. /// Errors list. /// The result. internal Collection ExecuteShellCommand(Runspace runspace, Command command, bool useDomainController, out object[] errors) { HostedSolutionLog.LogStart("ExecuteShellCommand"); var errorList = new List(); if (useDomainController) { var dc = new CommandParameter("DomainController", PrimaryDomainController); if (!command.Parameters.Contains(dc)) { command.Parameters.Add(dc); } } HostedSolutionLog.DebugCommand(command); Collection results; Pipeline pipeLine = runspace.CreatePipeline(); using (pipeLine) { pipeLine.Commands.Add(command); results = pipeLine.Invoke(); if (pipeLine.Error != null && pipeLine.Error.Count > 0) { foreach (object item in pipeLine.Error.ReadToEnd()) { errorList.Add(item); string errorMessage = string.Format("Invoke error: {0}", item); HostedSolutionLog.LogWarning(errorMessage); } } } errors = errorList.ToArray(); HostedSolutionLog.LogEnd("ExecuteShellCommand"); return results; } /// Executes shell command. /// The runspace. /// Scripts list. /// Errors list. /// The result. internal Collection ExecuteShellCommand(Runspace runspace, List scripts, out object[] errors) { HostedSolutionLog.LogStart("ExecuteShellCommand"); var errorList = new List(); Collection results; using (Pipeline pipeLine = runspace.CreatePipeline()) { foreach (string script in scripts) { pipeLine.Commands.AddScript(script); } results = pipeLine.Invoke(); if (pipeLine.Error != null && pipeLine.Error.Count > 0) { foreach (object item in pipeLine.Error.ReadToEnd()) { errorList.Add(item); string errorMessage = string.Format("Invoke error: {0}", item); HostedSolutionLog.LogWarning(errorMessage); throw new ArgumentException(scripts.First()); } } } errors = errorList.ToArray(); HostedSolutionLog.LogEnd("ExecuteShellCommand"); return results; } /// Gets PSObject property value. /// The object. /// The property name. /// The property value. internal object GetPSObjectProperty(PSObject obj, string name) { return obj.Members[name].Value; } #endregion #region Transactions /// Starts the transaction. /// The transaction. internal LyncTransaction StartTransaction() { return new LyncTransaction(); } /// Rollbacks the transaction. /// The transaction. internal void RollbackTransaction(LyncTransaction transaction) { HostedSolutionLog.LogStart("RollbackTransaction"); Runspace runspace = null; try { runspace = OpenRunspace(); for (int i = transaction.Actions.Count - 1; i > -1; i--) { try { RollbackAction(transaction.Actions[i], runspace); } catch (Exception ex) { HostedSolutionLog.LogError("Rollback error", ex); } } } catch (Exception ex) { HostedSolutionLog.LogError("Rollback error", ex); } finally { CloseRunspace(runspace); } HostedSolutionLog.LogEnd("RollbackTransaction"); } /// Rollbacks lync action. /// The action. /// The runspace. private void RollbackAction(TransactionAction action, Runspace runspace) { HostedSolutionLog.LogInfo("Rollback action: {0}", action.ActionType); switch (action.ActionType) { case TransactionAction.TransactionActionTypes.LyncNewSipDomain: DeleteSipDomain(runspace, action.Id); break; case TransactionAction.TransactionActionTypes.LyncNewUser: DeleteUser(runspace, action.Id); break; case TransactionAction.TransactionActionTypes.LyncNewConferencingPolicy: DeleteConferencingPolicy(runspace, action.Id); break; case TransactionAction.TransactionActionTypes.LyncNewExternalAccessPolicy: DeleteExternalAccessPolicy(runspace, action.Id); break; case TransactionAction.TransactionActionTypes.LyncNewMobilityPolicy: DeleteMobilityPolicy(runspace, action.Id); break; } } #endregion #region Helpers /// Gets organizations AD path. /// The organization identifier. /// The organization AD path. internal string GetOrganizationPath(string organizationId) { var sb = new StringBuilder(); AppendOUPath(sb, organizationId); AppendOUPath(sb, RootOU); AppendDomainPath(sb, RootDomain); return sb.ToString(); } /// Appends organizational unit path. /// The string builder. /// The organizational unit. internal static void AppendOUPath(StringBuilder sb, string ou) { if (string.IsNullOrEmpty(ou)) { return; } string path = ou.Replace("/", "\\"); string[] parts = path.Split('\\'); for (int i = parts.Length - 1; i != -1; i--) { sb.Append("OU=").Append(parts[i]).Append(","); } } /// Appends domain path. /// The string builder. /// The domain name. internal static void AppendDomainPath(StringBuilder sb, string domain) { if (string.IsNullOrEmpty(domain)) { return; } string[] parts = domain.Split('.'); for (int i = 0; i < parts.Length; i++) { sb.Append("DC=").Append(parts[i]); if (i < (parts.Length - 1)) { sb.Append(","); } } } /// Adds AD prefix. /// The path. /// The result. internal string AddADPrefix(string path) { string dn = path; if (!dn.ToUpper().StartsWith("LDAP://")) { dn = string.Format("LDAP://{0}/{1}", PrimaryDomainController, dn); } return dn; } /// Deletes sip domain. /// The runspace. /// The identifier. internal void DeleteSipDomain(Runspace runspace, string id) { HostedSolutionLog.LogStart("DeleteSipDomain"); HostedSolutionLog.DebugInfo("SipDomain : {0}", id); var command = new Command("Remove-CsSipDomain"); command.Parameters.Add("Identity", id); command.Parameters.Add("Confirm", false); command.Parameters.Add("Force", true); ExecuteShellCommand(runspace, command, false); HostedSolutionLog.LogEnd("DeleteSipDomain"); } /// Deletes user. /// The runspace. /// The user UPN. internal void DeleteUser(Runspace runspace, string userUpn) { HostedSolutionLog.LogStart("DeleteUser"); HostedSolutionLog.DebugInfo("userUpn : {0}", userUpn); var command = new Command("Disable-CsUser"); command.Parameters.Add("Identity", userUpn); command.Parameters.Add("Confirm", false); ExecuteShellCommand(runspace, command, false); HostedSolutionLog.LogEnd("DeleteUser"); } /// Deletes conferencing policy. /// The runspace. /// The policy name. internal void DeleteConferencingPolicy(Runspace runspace, string policyName) { HostedSolutionLog.LogStart("DeleteConferencingPolicy"); HostedSolutionLog.DebugInfo("policyName : {0}", policyName); var command = new Command("Remove-CsConferencingPolicy"); command.Parameters.Add("Identity", policyName); command.Parameters.Add("Confirm", false); command.Parameters.Add("Force", true); ExecuteShellCommand(runspace, command, false); HostedSolutionLog.LogEnd("DeleteConferencingPolicy"); } /// Deletes external access policy. /// The runspace. /// The policy name. internal void DeleteExternalAccessPolicy(Runspace runspace, string policyName) { HostedSolutionLog.LogStart("DeleteExternalAccessPolicy"); HostedSolutionLog.DebugInfo("policyName : {0}", policyName); var command = new Command("Remove-CsExternalAccessPolicy"); command.Parameters.Add("Identity", policyName); command.Parameters.Add("Confirm", false); command.Parameters.Add("Force", true); ExecuteShellCommand(runspace, command, false); HostedSolutionLog.LogEnd("DeleteExternalAccessPolicy"); } /// Deletes mobility policy. /// The runspace. /// The policy name. internal void DeleteMobilityPolicy(Runspace runspace, string policyName) { HostedSolutionLog.LogStart("DeleteMobilityPolicy"); HostedSolutionLog.DebugInfo("policyName : {0}", policyName); var command = new Command("Remove-CsMobilityPolicy"); command.Parameters.Add("Identity", policyName); command.Parameters.Add("Confirm", false); command.Parameters.Add("Force", true); ExecuteShellCommand(runspace, command, false); HostedSolutionLog.LogEnd("DeleteMobilityPolicy"); } /// Creates simple url. /// The runspace. /// The identifier. internal void CreateSimpleUrl(Runspace runspace, Guid id) { var command = new Command("Get-CsSipDomain"); Collection sipDomains = ExecuteShellCommand(runspace, command, false); IList SimpleUrls = new List(); foreach (PSObject domain in sipDomains) { var d = (string) GetPSObjectProperty(domain, "Name"); string Url = SimpleUrlRoot + d; command = new Command("New-CsSimpleUrlEntry"); command.Parameters.Add("Url", Url); Collection simpleUrlEntry = ExecuteShellCommand(runspace, command, false); command = new Command("New-CsSimpleUrl"); command.Parameters.Add("Component", "meet"); command.Parameters.Add("Domain", d); command.Parameters.Add("SimpleUrl", simpleUrlEntry[0]); command.Parameters.Add("ActiveUrl", Url); Collection simpleUrl = ExecuteShellCommand(runspace, command, false); SimpleUrls.Add(simpleUrl[0]); } command = new Command("Set-CsSimpleUrlConfiguration"); command.Parameters.Add("Identity", "Global"); //command.Parameters.Add("Tenant", id); command.Parameters.Add("SimpleUrl", SimpleUrls); ExecuteShellCommand(runspace, command, false); } #endregion #endregion } }