/* 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
///
/// optional - "version_min" attribute on the "XenServerPowerShellCmd" tag
///
public readonly Version SnapInMinVersion;
#pragma warning disable 649, 169
///
/// optional - "version_max" attribute on the "XenServerPowerShellCmd" tag
///
public readonly Version SnapInMaxVersion;
#pragma warning restore 649, 169
///
/// optional - "debug" attribute on the "XenServerPowerShellCmd" tag
///
public readonly bool Debug;
///
/// optional - "function" attribute on the "PowerShellCmd" tag
///
public readonly string Function;
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();
}
}
}