/* Copyright (c) Citrix Systems Inc. * 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. * * 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.Diagnostics; using System.IO; using System.Text; using System.Xml; using XenAdmin.Core; using XenAPI; namespace XenAdmin.Plugins { internal class XenServerPowershellCmd : ShellCmd { // TODO: CA:40580: These version numbers are not checked anywhere at the moment, and thus are not documented as part of the plugin spec public readonly Version SnapInMinVersion; // optional - "version_min" attribute on the "XenServerPowerShellCmd" tag public readonly Version SnapInMaxVersion; // optional - "version_max" attribute on the "XenServerPowerShellCmd" tag public readonly bool Debug; // optional - "debug" attribute on the "XenServerPowerShellCmd" tag public readonly string Function; // optional - "function" attribute on the "PowerShellCmd" tag public const string ATT_DEBUG = "debug"; public const string ATT_VERSION_MIN = "version_min"; public const string ATT_VERSION_MAX = "version_max"; public const string ATT_FUNCTION = "function"; private const string XEN_PARAM_ARRAY_VAR_NAME = "ObjInfoArray"; private const string XEN_PARAM_ARRAY_HASH_KEY_URL = "url"; private const string XEN_PARAM_ARRAY_HASH_KEY_SESSION_REF = "sessionRef"; private const string XEN_PARAM_ARRAY_HASH_KEY_CLASS = "class"; private const string XEN_PARAM_ARRAY_HASH_KEY_OBJECT_UUID = "objUuid"; private const string USER_PARAM_ARRAY_VAR_NAME = "ParamArray"; // This is the parameter to a string.Format call, so the double braces come out as single braces in the PowerShell. private const string DebugFunction = "function __XenCenter_DebugPlugin {{" + " trap [Exception] {{" + " write-host $($_.Exception.GetType().FullName); " + " write-host $($_.Exception.Message);" + " read-host \"[Press Enter to Exit]\" " + " }};" + " {0};" + "}};" + "__XenCenter_DebugPlugin"; private const string InvokeExpression = "Invoke-Expression $([System.String]::Join([System.Environment]::NewLine, (Get-Content -Path \"{0}\"))); {1}"; public XenServerPowershellCmd(XmlNode node, List extraParams) : base(node, extraParams) { Debug = Helpers.GetBoolXmlAttribute(node, ATT_DEBUG, false); string versionString = Helpers.GetStringXmlAttribute(node, ATT_VERSION_MIN); if (versionString != null) SnapInMinVersion = new Version(versionString); versionString = Helpers.GetStringXmlAttribute(node, ATT_VERSION_MAX); if (versionString != null) SnapInMinVersion = new Version(versionString); Function = Helpers.GetStringXmlAttribute(node, ATT_FUNCTION); Registry.CheckXenServerPSSnapIn(); } public override Process CreateProcess(List procParams, IList targets) { Registry.CheckXenServerPSSnapIn(); string snapin = Registry.XenServerSnapInLocation(); // execute the initialize script // get ourselves in the correct directory // put the parameters in the objInfoArray variable // exectute the plugin, with debugging if required string command = string.Format( "& {{" + " . \"{0}\";" + " {1}" + "}}", Path.Combine(Path.GetDirectoryName(snapin), "Initialize-Environment.ps1"), MakeInvocationExpression(Filename, Function, procParams, Params, targets, Debug)); // finally we escape the entire command statement again as it is being passed into the -Command parameter in quotes command = EscapeQuotes(EscapeBackSlashes(command)); Process proc = new Process(); proc.StartInfo.FileName = PluginDescriptor.PowerShellExecutable; proc.StartInfo.Arguments = string.Format("-PSConsoleFile \"{0}\" -NoLogo -Command \"{1}\"", EscapeQuotes(snapin), command); proc.StartInfo.UseShellExecute = false; proc.StartInfo.CreateNoWindow = !Window; proc.StartInfo.WindowStyle = !Window ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal; return proc; } public static string EscapeQuotes(string s) { // replacing " with \" return s.Replace("\"", "\\\""); } public static string EscapeBackSlashes(string s) { // replacing \ with \\ return s.Replace("\\", "\\\\"); } public static string MakeInvocationExpression(string filename, string function, IList proc_params, IList extra_params, IList objs, bool debug) { string expression = string.Format(InvokeExpression, Placeholders.Substitute(filename, objs), Placeholders.Substitute(function ?? "", objs)); string xenArrayStatement = XenArrayStatement(proc_params); string extraArrayStatement = ExtraArrayStatement(extra_params, objs); return string.Format("cd \"{0}\"; {1} {2} {3};", Program.AssemblyDir, xenArrayStatement, extraArrayStatement, debug ? string.Format(DebugFunction, expression) : expression); } private static string XenArrayStatement(IList procParams) { // check how many objects we are passing in, each is a set of 4 strings (url, uuid, obj classname, obj uuid) int count = (int)(procParams.Count / 4); // now we form a statement that will initialise a powershell array in the format (a,b,c,d),(a2,b2,c2,d2),(a3,b3,c3,d3) e.t.c StringBuilder sb = new StringBuilder(); sb.Append(string.Format("${0}=@(", XEN_PARAM_ARRAY_VAR_NAME)); for (int i = 0; i < count; i++) { if (i > 0) sb.Append(','); int index = i * 4; sb.AppendFormat(" @{{{0}=\"{1}\"; {2}=\"{3}\"; {4}=\"{5}\"; {6}=\"{7}\"}}", XEN_PARAM_ARRAY_HASH_KEY_URL, EscapeQuotes(EscapeBackSlashes(procParams[index])), XEN_PARAM_ARRAY_HASH_KEY_SESSION_REF, EscapeQuotes(EscapeBackSlashes(procParams[index + 1])), XEN_PARAM_ARRAY_HASH_KEY_CLASS, EscapeQuotes(EscapeBackSlashes(procParams[index + 2])), XEN_PARAM_ARRAY_HASH_KEY_OBJECT_UUID, EscapeQuotes(EscapeBackSlashes(procParams[index + 3]))); } sb.Append(");"); return sb.ToString(); } private static string ExtraArrayStatement(IList extraParams, IList objs) { // now we form a statement that will initialise a powershell array in the format (a,b,c,d,e,f,g) StringBuilder sb = new StringBuilder(); sb.Append(string.Format("${0}=@(", USER_PARAM_ARRAY_VAR_NAME)); for (int i = 0; i < extraParams.Count; i++) { if (i > 0) sb.Append(','); sb.Append('"'); sb.Append(EscapeQuotes(EscapeBackSlashes(Placeholders.Substitute(extraParams[i], objs)))); sb.Append('"'); } sb.Append(");"); return sb.ToString(); } } }