diff --git a/contrib/windows-agent/ExecutionPlanGenerator/App.config b/contrib/windows-agent/ExecutionPlanGenerator/App.config
new file mode 100644
index 00000000..8e156463
--- /dev/null
+++ b/contrib/windows-agent/ExecutionPlanGenerator/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/contrib/windows-agent/ExecutionPlanGenerator/ExecutionPlanGenerator.csproj b/contrib/windows-agent/ExecutionPlanGenerator/ExecutionPlanGenerator.csproj
new file mode 100644
index 00000000..5c63e534
--- /dev/null
+++ b/contrib/windows-agent/ExecutionPlanGenerator/ExecutionPlanGenerator.csproj
@@ -0,0 +1,62 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {501BE151-4B8C-4355-88DC-3AEF1921B2D7}
+ Exe
+ Properties
+ Mirantis.Murano
+ ExecutionPlanGenerator
+ v4.5
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/contrib/windows-agent/ExecutionPlanGenerator/Program.cs b/contrib/windows-agent/ExecutionPlanGenerator/Program.cs
new file mode 100644
index 00000000..798c2ae5
--- /dev/null
+++ b/contrib/windows-agent/ExecutionPlanGenerator/Program.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace Mirantis.Murano
+{
+ class Command
+ {
+ public string Name { get; set; }
+ public Dictionary Arguments { get; set; }
+ }
+ class ExecutionPlan
+ {
+ public List Scripts { get; set; }
+ public List Commands { get; set; }
+ public int RebootOnCompletion { get; set; }
+ }
+
+
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ if (args.Length < 1 || args.Length > 2)
+ {
+ Console.WriteLine("Usage: ExecutionPlanGenerator inputfile [outputfile]");
+ return;
+ }
+
+ var outFile = args.Length == 2 ? args[1] : null;
+
+ var plan = new ExecutionPlan {
+ Scripts = new List(),
+ Commands = new List()
+ };
+
+
+
+ var lines = File.ReadAllLines(args[0]);
+
+
+ foreach (var statement in lines
+ .Select(t => t.Split(new[] { ' ', '\t' }, 2))
+ .Where(t => t.Length == 2)
+ .Select(t => new Tuple(t[0].Trim().ToLower(), t[1].Trim())))
+ {
+ switch (statement.Item1)
+ {
+ case "include":
+ Include(statement.Item2, plan, args[0]);
+ break;
+ case "call":
+ Call(statement.Item2, plan);
+ break;
+ case "reboot":
+ plan.RebootOnCompletion = int.Parse(statement.Item2);
+ break;
+ case "out":
+ if (args.Length < 2)
+ {
+ var path = statement.Item2;
+ if (!Path.IsPathRooted(path))
+ {
+ path = Path.Combine(Path.GetDirectoryName(args[0]), path);
+ }
+ outFile = path;
+ }
+ break;
+ }
+ }
+
+ var data = JsonConvert.SerializeObject(plan, Formatting.Indented);
+ if (outFile == null)
+ {
+ Console.WriteLine(data);
+ }
+ else
+ {
+ File.WriteAllText(outFile, data);
+ }
+ }
+
+ private static void Call(string line, ExecutionPlan plan)
+ {
+ var parts = line.Split(new[] { ' ', '\t'}, 2);
+ var command = new Command() {
+ Name = parts[0].Trim(),
+ Arguments = new Dictionary()
+ };
+
+
+ if (parts.Length == 2)
+ {
+ foreach (var x in parts[1]
+ .Split(',')
+ .Select(t => t.Split('='))
+ .Where(t => t.Length == 2)
+ .Select(t => new KeyValuePair(t[0].Trim(), t[1].Trim())))
+ {
+ object value = null;
+ long num;
+ bool boolean;
+ if (x.Value.StartsWith("\""))
+ {
+ value = x.Value.Substring(1, x.Value.Length - 2);
+ }
+ else if (long.TryParse(x.Value, out num))
+ {
+ value = num;
+ }
+ else if (bool.TryParse(x.Value, out boolean))
+ {
+ value = boolean;
+ }
+ else
+ {
+ continue;
+ }
+ command.Arguments.Add(x.Key, value);
+ }
+ }
+ plan.Commands.Add(command);
+ }
+
+ private static void Include(string file, ExecutionPlan plan, string dslPath)
+ {
+ var path = file;
+ if (!Path.IsPathRooted(file))
+ {
+ path = Path.Combine(Path.GetDirectoryName(dslPath), path);
+ }
+
+ var text = File.ReadAllText(path, Encoding.UTF8);
+ plan.Scripts.Add(Convert.ToBase64String(Encoding.UTF8.GetBytes(text)));
+ }
+ }
+}
diff --git a/contrib/windows-agent/ExecutionPlanGenerator/Properties/AssemblyInfo.cs b/contrib/windows-agent/ExecutionPlanGenerator/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..54a148f4
--- /dev/null
+++ b/contrib/windows-agent/ExecutionPlanGenerator/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ConsoleApplication1")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ConsoleApplication1")]
+[assembly: AssemblyCopyright("Copyright © 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("9aab688a-ce5f-402e-8891-2d7b4ae85ea3")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/contrib/windows-agent/ExecutionPlanGenerator/packages.config b/contrib/windows-agent/ExecutionPlanGenerator/packages.config
new file mode 100644
index 00000000..b82a8b0d
--- /dev/null
+++ b/contrib/windows-agent/ExecutionPlanGenerator/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/contrib/windows-agent/README.rst b/contrib/windows-agent/README.rst
new file mode 100644
index 00000000..743eabab
--- /dev/null
+++ b/contrib/windows-agent/README.rst
@@ -0,0 +1,8 @@
+Murano Windows Agent
+====================
+
+Murano Windows Agent is an initial version of Murano Agent.
+Currently, it's outdated and not supported.
+
+The main difference with the new Python agent is support of running Powershell.
+After this support will be added to Python Agent, Windows Agent will be dropped.
diff --git a/contrib/windows-agent/Tools/NuGet.exe b/contrib/windows-agent/Tools/NuGet.exe
new file mode 100644
index 00000000..4645f4b3
Binary files /dev/null and b/contrib/windows-agent/Tools/NuGet.exe differ
diff --git a/contrib/windows-agent/WindowsAgent.sln b/contrib/windows-agent/WindowsAgent.sln
new file mode 100644
index 00000000..b77d57cb
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent.sln
@@ -0,0 +1,26 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExecutionPlanGenerator", "ExecutionPlanGenerator\ExecutionPlanGenerator.csproj", "{501BE151-4B8C-4355-88DC-3AEF1921B2D7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.Build.0 = Release|Any CPU
+ {501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/contrib/windows-agent/WindowsAgent/App.config b/contrib/windows-agent/WindowsAgent/App.config
new file mode 100644
index 00000000..85d96b37
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/App.config
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/windows-agent/WindowsAgent/ExecutionPlan.cs b/contrib/windows-agent/WindowsAgent/ExecutionPlan.cs
new file mode 100644
index 00000000..9761d36a
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/ExecutionPlan.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mirantis.Murano.WindowsAgent
+{
+ class ExecutionPlan
+ {
+ public class Command
+ {
+ public string Name { get; set; }
+ public Dictionary Arguments { get; set; }
+ }
+
+ public string[] Scripts { get; set; }
+ public LinkedList Commands { get; set; }
+ public int RebootOnCompletion { get; set; }
+ }
+}
diff --git a/contrib/windows-agent/WindowsAgent/MqMessage.cs b/contrib/windows-agent/WindowsAgent/MqMessage.cs
new file mode 100644
index 00000000..76e2faab
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/MqMessage.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Mirantis.Murano.WindowsAgent
+{
+ class MqMessage
+ {
+ private readonly Action ackFunc;
+
+ public MqMessage(Action ackFunc)
+ {
+ this.ackFunc = ackFunc;
+ }
+
+ public MqMessage()
+ {
+ }
+
+ public string Body { get; set; }
+ public string Id { get; set; }
+
+ public void Ack()
+ {
+ ackFunc();
+ }
+ }
+}
diff --git a/contrib/windows-agent/WindowsAgent/PlanExecutor.cs b/contrib/windows-agent/WindowsAgent/PlanExecutor.cs
new file mode 100644
index 00000000..628eeb4d
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/PlanExecutor.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Text;
+using Newtonsoft.Json.Linq;
+using NLog;
+using Newtonsoft.Json;
+
+namespace Mirantis.Murano.WindowsAgent
+{
+ class PlanExecutor
+ {
+ private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+
+ class ExecutionResult
+ {
+ public bool IsException { get; set; }
+ public object Result { get; set; }
+ }
+
+ private readonly string path;
+
+ public PlanExecutor(string path)
+ {
+ this.path = path;
+ }
+
+ public bool RebootNeeded { get; set; }
+
+ public void Execute()
+ {
+ RebootNeeded = false;
+ var resultPath = this.path + ".result";
+ Runspace runSpace = null;
+ try
+ {
+ var plan = JsonConvert.DeserializeObject(File.ReadAllText(this.path));
+ List currentResults = null;
+ try
+ {
+ currentResults = File.Exists(resultPath) ?
+ JsonConvert.DeserializeObject>(File.ReadAllText(resultPath)) :
+ new List();
+ }
+ catch(Exception exception)
+ {
+ Log.WarnException("Cannot deserialize previous execution result", exception);
+ currentResults = new List();
+ }
+
+ runSpace = RunspaceFactory.CreateRunspace();
+ runSpace.Open();
+
+ var runSpaceInvoker = new RunspaceInvoke(runSpace);
+ runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
+ if (plan.Scripts != null)
+ {
+ var index = 0;
+ foreach (var script in plan.Scripts)
+ {
+ runSpaceInvoker.Invoke(Encoding.UTF8.GetString(Convert.FromBase64String(script)));
+ Log.Debug("Loaded script #{0}", ++index);
+ }
+ }
+
+ while (plan.Commands != null && plan.Commands.Any())
+ {
+ var command = plan.Commands.First();
+ Log.Debug("Preparing to execute command {0}", command.Name);
+
+ var pipeline = runSpace.CreatePipeline();
+ var psCommand = new Command(command.Name);
+ if (command.Arguments != null)
+ {
+ foreach (var kvp in command.Arguments)
+ {
+ var value = ConvertArgument(kvp.Value);
+ psCommand.Parameters.Add(kvp.Key, value);
+ }
+ }
+
+ Log.Info("Executing {0} {1}", command.Name, string.Join(" ",
+ (command.Arguments ?? new Dictionary()).Select(
+ t => string.Format("{0}={1}", t.Key, t.Value == null ? "null" : t.Value.ToString()))));
+
+ pipeline.Commands.Add(psCommand);
+
+ try
+ {
+ var result = pipeline.Invoke();
+ Log.Debug("Command {0} executed", command.Name);
+ if (result != null)
+ {
+ currentResults.Add(new ExecutionResult {
+ IsException = false,
+ Result = result.Select(SerializePsObject).Where(obj => obj != null).ToList()
+ });
+ }
+ }
+ catch (Exception exception)
+ {
+ object additionInfo = null;
+ if (exception is ActionPreferenceStopException)
+ {
+ var apse = exception as ActionPreferenceStopException;
+ if (apse.ErrorRecord != null)
+ {
+ additionInfo = new {
+ ScriptStackTrace = apse.ErrorRecord.ScriptStackTrace,
+ PositionMessage = apse.ErrorRecord.InvocationInfo.PositionMessage
+ };
+ exception = apse.ErrorRecord.Exception;
+ }
+ }
+
+
+ Log.WarnException("Exception while executing command " + command.Name, exception);
+ currentResults.Add(new ExecutionResult
+ {
+ IsException = true,
+ Result = new[] {
+ exception.GetType().FullName, exception.Message, command.Name, additionInfo
+ }
+ });
+ break;
+ }
+ finally
+ {
+ plan.Commands.RemoveFirst();
+ File.WriteAllText(path, JsonConvert.SerializeObject(plan));
+ File.WriteAllText(resultPath, JsonConvert.SerializeObject(currentResults));
+ }
+ }
+ runSpace.Close();
+ var executionResult = JsonConvert.SerializeObject(new ExecutionResult {
+ IsException = false,
+ Result = currentResults
+ }, Formatting.Indented);
+
+ if (plan.RebootOnCompletion > 0)
+ {
+ if (plan.RebootOnCompletion == 1)
+ {
+ RebootNeeded = !currentResults.Any(t => t.IsException);
+ }
+ else
+ {
+ RebootNeeded = true;
+ }
+ }
+ File.WriteAllText(resultPath, executionResult);
+ }
+ catch (Exception exception)
+ {
+ Log.WarnException("Exception while processing execution plan", exception);
+ File.WriteAllText(resultPath, JsonConvert.SerializeObject(new ExecutionResult {
+ IsException = true,
+ Result = exception.Message
+ }, Formatting.Indented));
+ }
+ finally
+ {
+ if (runSpace != null)
+ {
+ try
+ {
+ runSpace.Close();
+ }
+ catch
+ {}
+ }
+ Log.Debug("Finished processing of execution plan");
+ }
+ }
+
+ private static object ConvertArgument(object arg)
+ {
+ if (arg is JArray)
+ {
+ var array = arg as JArray;
+ return array.Select(ConvertArgument).ToArray();
+ }
+ else if (arg is JValue)
+ {
+ var value = (JValue) arg;
+ return value.Value;
+ }
+ else if (arg is JObject)
+ {
+ var dict = (JObject)arg;
+ var result = new Hashtable();
+ foreach (var item in dict)
+ {
+ result.Add(item.Key, ConvertArgument(item.Value));
+ }
+ return result;
+ }
+ return arg;
+ }
+
+ private static object SerializePsObject(PSObject obj)
+ {
+ if (obj.BaseObject is PSCustomObject)
+ {
+ var result = new Dictionary();
+ foreach (var property in obj.Properties.Where(p => p.IsGettable))
+ {
+ try
+ {
+ result[property.Name] = property.Value.ToString();
+ }
+ catch
+ {
+ }
+ }
+ return result;
+ }
+ else if (obj.BaseObject is IEnumerable)
+ {
+ return ((IEnumerable) obj.BaseObject).Select(SerializePsObject).ToArray();
+ }
+ else
+ {
+ return obj.BaseObject;
+ }
+ }
+ }
+
+}
diff --git a/contrib/windows-agent/WindowsAgent/Program.cs b/contrib/windows-agent/WindowsAgent/Program.cs
new file mode 100644
index 00000000..5aeba8a8
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/Program.cs
@@ -0,0 +1,168 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Net;
+using System.Text;
+using System.Threading;
+using NLog;
+
+namespace Mirantis.Murano.WindowsAgent
+{
+ [DisplayName("Murano Agent")]
+ sealed public class Program : WindowsService
+ {
+ private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+ private volatile bool stop;
+ private Thread thread;
+ private RabbitMqClient rabbitMqClient;
+ private int delayFactor = 1;
+ private string plansDir;
+
+ static void Main(string[] args)
+ {
+ Start(new Program(), args);
+ }
+
+ protected override void OnStart(string[] args)
+ {
+ base.OnStart(args);
+
+ Log.Info("Version 0.5.3");
+
+ this.rabbitMqClient = new RabbitMqClient();
+
+ var basePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
+ this.plansDir = Path.Combine(basePath, "plans");
+
+
+ if (!Directory.Exists(plansDir))
+ {
+ Directory.CreateDirectory(plansDir);
+ }
+
+ this.thread = new Thread(Loop);
+ this.thread.Start();
+ }
+
+ void Loop()
+ {
+ const string unknownName = "unknown";
+ while (!stop)
+ {
+ try
+ {
+ foreach (var file in Directory.GetFiles(this.plansDir, "*.json.result")
+ .Where(file => !File.Exists(Path.Combine(this.plansDir, Path.GetFileNameWithoutExtension(file)))))
+ {
+ var id = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(file)) ?? unknownName;
+ if (id.Equals(unknownName, StringComparison.InvariantCultureIgnoreCase))
+ {
+ id = "";
+ }
+
+ var result = File.ReadAllText(file);
+ Log.Info("Sending results for {0}", id ?? unknownName);
+ rabbitMqClient.SendResult(new MqMessage { Body = result, Id = id });
+ File.Delete(file);
+ }
+
+ var path = Directory.EnumerateFiles(this.plansDir, "*.json").FirstOrDefault();
+ if (path == null)
+ {
+ var message = rabbitMqClient.GetMessage();
+ var id = message.Id;
+ if(string.IsNullOrEmpty(id))
+ {
+ id = unknownName;
+ }
+
+ path = Path.Combine(this.plansDir, string.Format("{0}.json", id));
+ File.WriteAllText(path, message.Body);
+ Log.Info("Received new execution plan {0}", id);
+ message.Ack();
+ }
+ else
+ {
+ var id = Path.GetFileNameWithoutExtension(path);
+ Log.Info("Executing exising plan {0}", id);
+ }
+ var executor = new PlanExecutor(path);
+ executor.Execute();
+ File.Delete(path);
+ delayFactor = 1;
+
+ if (stop) break;
+ if (executor.RebootNeeded)
+ {
+ Reboot();
+ }
+ }
+ catch (Exception exception)
+ {
+ WaitOnException(exception);
+ }
+
+ }
+
+ }
+
+ private void Reboot()
+ {
+ Log.Info("Going for reboot!!");
+ LogManager.Flush();
+ /*try
+ {
+ System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0");
+ }
+ catch (Exception ex)
+ {
+ Log.ErrorException("Cannot execute shutdown.exe", ex);
+ }*/
+
+
+ try
+ {
+ PowerShell.Create().AddCommand("Restart-Computer").AddParameter("Force").Invoke();
+ }
+ catch (Exception exception)
+ {
+
+ Log.FatalException("Reboot exception", exception);
+ }
+ finally
+ {
+ Log.Info("Waiting for reboot");
+ for (var i = 0; i < 10 * 60 * 5 && !stop; i++)
+ {
+ Thread.Sleep(100);
+ }
+ Log.Info("Done waiting for reboot");
+ }
+
+ }
+
+ private void WaitOnException(Exception exception)
+ {
+ if (stop) return;
+ Log.WarnException("Exception in main loop", exception);
+ var i = 0;
+ while (!stop && i < 10 * (delayFactor * delayFactor))
+ {
+ Thread.Sleep(100);
+ i++;
+ }
+ delayFactor = Math.Min(delayFactor + 1, 6);
+ }
+
+ protected override void OnStop()
+ {
+ stop = true;
+ this.rabbitMqClient.Dispose();
+ base.OnStop();
+ }
+
+ }
+}
diff --git a/contrib/windows-agent/WindowsAgent/Properties/AssemblyInfo.cs b/contrib/windows-agent/WindowsAgent/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..f7d169bb
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("WindowsAgent")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("WindowsAgent")]
+[assembly: AssemblyCopyright("Copyright © 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("9591bf2c-f38b-47e0-a39d-ea9849356371")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/contrib/windows-agent/WindowsAgent/RabbitMqClient.cs b/contrib/windows-agent/WindowsAgent/RabbitMqClient.cs
new file mode 100644
index 00000000..6d42b51e
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/RabbitMqClient.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Linq;
+using System.Net;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+using NLog;
+using RabbitMQ.Client;
+
+namespace Mirantis.Murano.WindowsAgent
+{
+ class RabbitMqClient : IDisposable
+ {
+ private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+ private static readonly ConnectionFactory connectionFactory;
+ private IConnection currentConnecton;
+
+ static RabbitMqClient()
+ {
+ var ssl = new SslOption {
+ Enabled = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.ssl"] ?? "false"),
+ Version = SslProtocols.Default,
+ AcceptablePolicyErrors = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.allowInvalidCA"] ?? "true") ?
+ SslPolicyErrors.RemoteCertificateChainErrors : SslPolicyErrors.None
+ };
+
+ var sslServerName = ConfigurationManager.AppSettings["rabbitmq.sslServerName"] ?? "";
+ ssl.ServerName = sslServerName;
+ if (String.IsNullOrWhiteSpace(sslServerName))
+ {
+ ssl.AcceptablePolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch;
+ }
+
+ connectionFactory = new ConnectionFactory {
+ HostName = ConfigurationManager.AppSettings["rabbitmq.host"] ?? "localhost",
+ UserName = ConfigurationManager.AppSettings["rabbitmq.user"] ?? "guest",
+ Password = ConfigurationManager.AppSettings["rabbitmq.password"] ??"guest",
+ Protocol = Protocols.DefaultProtocol,
+ VirtualHost = ConfigurationManager.AppSettings["rabbitmq.vhost"] ?? "/",
+ Port = int.Parse(ConfigurationManager.AppSettings["rabbitmq.port"] ?? "5672"),
+ RequestedHeartbeat = 10,
+ Ssl = ssl
+ };
+ }
+
+ public RabbitMqClient()
+ {
+
+ }
+
+ public MqMessage GetMessage()
+ {
+ var queueName = ConfigurationManager.AppSettings["rabbitmq.inputQueue"] ?? Dns.GetHostName().ToLower();
+ try
+ {
+ IConnection connection = null;
+ lock (this)
+ {
+ connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
+ }
+ var session = connection.CreateModel();
+ session.BasicQos(0, 1, false);
+ //session.QueueDeclare(queueName, true, false, false, null);
+ var consumer = new QueueingBasicConsumer(session);
+ var consumeTag = session.BasicConsume(queueName, false, consumer);
+ var e = (RabbitMQ.Client.Events.BasicDeliverEventArgs) consumer.Queue.Dequeue();
+ Action ackFunc = delegate {
+ session.BasicAck(e.DeliveryTag, false);
+ session.BasicCancel(consumeTag);
+ session.Close();
+ };
+
+ return new MqMessage(ackFunc) {
+ Body = Encoding.UTF8.GetString(e.Body),
+ Id = e.BasicProperties.MessageId
+ };
+ }
+ catch (Exception exception)
+ {
+
+ Dispose();
+ throw;
+ }
+ }
+
+ public void SendResult(MqMessage message)
+ {
+ var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? "";
+ var resultRoutingKey = ConfigurationManager.AppSettings["rabbitmq.resultRoutingKey"] ?? "-execution-results";
+ bool durable = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.durableMessages"] ?? "true");
+
+ try
+ {
+ IConnection connection = null;
+ lock (this)
+ {
+ connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
+ }
+ var session = connection.CreateModel();
+ /*if (!string.IsNullOrEmpty(resultQueue))
+ {
+ //session.QueueDeclare(resultQueue, true, false, false, null);
+ if (!string.IsNullOrEmpty(exchangeName))
+ {
+ session.ExchangeBind(exchangeName, resultQueue, resultQueue);
+ }
+ }*/
+ var basicProperties = session.CreateBasicProperties();
+ basicProperties.SetPersistent(durable);
+ basicProperties.MessageId = message.Id;
+ basicProperties.ContentType = "application/json";
+ session.BasicPublish(exchangeName, resultRoutingKey, basicProperties, Encoding.UTF8.GetBytes(message.Body));
+ session.Close();
+ }
+ catch (Exception)
+ {
+ Dispose();
+ throw;
+ }
+ }
+
+ public void Dispose()
+ {
+ lock (this)
+ {
+ try
+ {
+ if (this.currentConnecton != null)
+ {
+ this.currentConnecton.Close();
+ }
+ }
+ catch
+ {
+ }
+ finally
+ {
+ this.currentConnecton = null;
+ }
+ }
+ }
+ }
+}
diff --git a/contrib/windows-agent/WindowsAgent/SampleExecutionPlan.json b/contrib/windows-agent/WindowsAgent/SampleExecutionPlan.json
new file mode 100644
index 00000000..9522b70a
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/SampleExecutionPlan.json
@@ -0,0 +1,37 @@
+{
+ "Scripts":
+ [
+ "ZnVuY3Rpb24gdDMgeyAxMjsgcmV0dXJuICJ0ZXN0IiB9",
+ "ZnVuY3Rpb24gTmV3LVBlcnNvbigpDQp7DQogIHBhcmFtICgkRmlyc3ROYW1lLCAkTGFzdE5hbWUsICRQaG9uZSkNCg0KICAkcGVyc29uID0gbmV3LW9iamVjdCBQU09iamVjdA0KDQogICRwZXJzb24gfCBhZGQtbWVtYmVyIC10eXBlIE5vdGVQcm9wZXJ0eSAtTmFtZSBGaXJzdCAtVmFsdWUgJEZpcnN0TmFtZQ0KICAkcGVyc29uIHwgYWRkLW1lbWJlciAtdHlwZSBOb3RlUHJvcGVydHkgLU5hbWUgTGFzdCAtVmFsdWUgJExhc3ROYW1lDQogICRwZXJzb24gfCBhZGQtbWVtYmVyIC10eXBlIE5vdGVQcm9wZXJ0eSAtTmFtZSBQaG9uZSAtVmFsdWUgJFBob25lDQoNCiAgcmV0dXJuICRwZXJzb24NCn0=",
+ "ZnVuY3Rpb24gVGVzdFRocm93KCkNCnsNCglUaHJvdyBbc3lzdGVtLkluZGV4T3V0T2ZSYW5nZUV4Y2VwdGlvbl0gDQp9"
+ ],
+ "Commands" :
+ [
+ {
+ "Name": "New-Person",
+ "Arguments" :
+ {
+ "FirstName": "MyFirstName",
+ "LastName": "MyLastName",
+ "Phone": "123-456"
+ }
+
+ },
+ {
+ "Name": "t3",
+ "Arguments" :
+ {
+ }
+
+ },
+ {
+ "Name": "Get-Date",
+
+ },
+ {
+ "Name": "TestThrow",
+
+ }
+ ],
+ "RebootOnCompletion": 0
+}
\ No newline at end of file
diff --git a/contrib/windows-agent/WindowsAgent/ServiceManager.cs b/contrib/windows-agent/WindowsAgent/ServiceManager.cs
new file mode 100644
index 00000000..6bf4d494
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/ServiceManager.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Configuration.Install;
+using System.Reflection;
+using System.ServiceProcess;
+using NLog;
+
+namespace Mirantis.Murano.WindowsAgent
+{
+ public class ServiceManager
+ {
+ private readonly string serviceName;
+
+ public ServiceManager(string serviceName)
+ {
+ this.serviceName = serviceName;
+ }
+
+ private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+
+ public bool Restart(string[] args, TimeSpan timeout)
+ {
+ var service = new ServiceController(serviceName);
+ try
+ {
+ var millisec1 = TimeSpan.FromMilliseconds(Environment.TickCount);
+
+ service.Stop();
+ service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
+ Log.Info("Service is stopped");
+
+ // count the rest of the timeout
+ var millisec2 = TimeSpan.FromMilliseconds(Environment.TickCount);
+ timeout = timeout - (millisec2 - millisec1);
+
+ service.Start(args);
+ service.WaitForStatus(ServiceControllerStatus.Running, timeout);
+ Log.Info("Service has started");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.ErrorException("Cannot restart service " + serviceName, ex);
+ return false;
+ }
+ }
+
+ public bool Stop(TimeSpan timeout)
+ {
+ var service = new ServiceController(serviceName);
+ try
+ {
+ service.Stop();
+ service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.ErrorException("Cannot stop service " + serviceName, ex);
+ return false;
+ }
+ }
+
+ public bool Start(string[] args, TimeSpan timeout)
+ {
+ var service = new ServiceController(serviceName);
+ try
+ {
+ service.Start(args);
+ service.WaitForStatus(ServiceControllerStatus.Running, timeout);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.ErrorException("Cannot start service " + serviceName, ex);
+ return false;
+ }
+ }
+
+ public bool Install()
+ {
+ try
+ {
+ ManagedInstallerClass.InstallHelper(
+ new string[] { Assembly.GetEntryAssembly().Location });
+ }
+ catch(Exception ex)
+ {
+ Log.ErrorException("Cannot install service " + serviceName, ex);
+ return false;
+ }
+ return true;
+ }
+
+ public bool Uninstall()
+ {
+ try
+ {
+ ManagedInstallerClass.InstallHelper(
+ new string[] { "/u", Assembly.GetEntryAssembly().Location });
+ }
+ catch (Exception ex)
+ {
+ Log.ErrorException("Cannot uninstall service " + serviceName, ex);
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+}
diff --git a/contrib/windows-agent/WindowsAgent/WindowsAgent.csproj b/contrib/windows-agent/WindowsAgent/WindowsAgent.csproj
new file mode 100644
index 00000000..bf9aba9a
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/WindowsAgent.csproj
@@ -0,0 +1,95 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F7E2A8D5-6D24-4651-A4BC-1024D59F4903}
+ Exe
+ Properties
+ Mirantis.Murano.WindowsAgent
+ WindowsAgent
+ v4.0
+ 512
+ Client
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ false
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+
+ ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll
+
+
+ ..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll
+
+
+ ..\packages\RabbitMQ.Client.3.0.2\lib\net30\RabbitMQ.Client.dll
+
+
+
+
+
+
+ False
+ C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Component
+
+
+
+
+
+ Component
+
+
+ Component
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)Tools\nuget install $(ProjectDir)packages.config -o $(SolutionDir)Packages
+
+
+
\ No newline at end of file
diff --git a/contrib/windows-agent/WindowsAgent/WindowsService.cs b/contrib/windows-agent/WindowsAgent/WindowsService.cs
new file mode 100644
index 00000000..1748e047
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/WindowsService.cs
@@ -0,0 +1,95 @@
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.ServiceProcess;
+using NLog;
+
+namespace Mirantis.Murano.WindowsAgent
+{
+ public abstract class WindowsService : ServiceBase
+ {
+ private static readonly Logger Log = LogManager.GetCurrentClassLogger();
+ public bool RunningAsService { get; private set; }
+
+ protected static void Start(WindowsService service, string[] arguments)
+ {
+ Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
+
+ if (arguments.Contains("/install", StringComparer.OrdinalIgnoreCase))
+ {
+ new ServiceManager(service.ServiceName).Install();
+ }
+ else if (arguments.Contains("/uninstall", StringComparer.OrdinalIgnoreCase))
+ {
+ new ServiceManager(service.ServiceName).Uninstall();
+ }
+ else if (arguments.Contains("/start", StringComparer.OrdinalIgnoreCase))
+ {
+ new ServiceManager(service.ServiceName).Start(Environment.GetCommandLineArgs(), TimeSpan.FromMinutes(1));
+ }
+ else if (arguments.Contains("/stop", StringComparer.OrdinalIgnoreCase))
+ {
+ new ServiceManager(service.ServiceName).Stop(TimeSpan.FromMinutes(1));
+ }
+ else if (arguments.Contains("/restart", StringComparer.OrdinalIgnoreCase))
+ {
+ new ServiceManager(service.ServiceName).Restart(Environment.GetCommandLineArgs(), TimeSpan.FromMinutes(1));
+ }
+ else if (!arguments.Contains("/console", StringComparer.OrdinalIgnoreCase))
+ {
+ service.RunningAsService = true;
+ Run(service);
+ }
+ else
+ {
+ try
+ {
+ service.RunningAsService = false;
+ Console.Title = service.ServiceName;
+ service.OnStart(Environment.GetCommandLineArgs());
+ service.WaitForExitSignal();
+ }
+ finally
+ {
+ service.OnStop();
+ service.Dispose();
+ }
+ }
+ }
+
+ protected WindowsService()
+ {
+ var displayNameAttribute =
+ this.GetType().GetCustomAttributes(typeof (DisplayNameAttribute), false).Cast().
+ FirstOrDefault();
+ if(displayNameAttribute != null)
+ {
+ ServiceName = displayNameAttribute.DisplayName;
+ }
+ }
+
+
+ protected virtual void WaitForExitSignal()
+ {
+ Console.WriteLine("Press ESC to exit");
+ while (Console.ReadKey(true).Key != ConsoleKey.Escape)
+ {
+ }
+ }
+
+ protected override void OnStart(string[] args)
+ {
+ Log.Info("Service {0} started", ServiceName);
+
+ base.OnStart(args);
+ }
+
+ protected override void OnStop()
+ {
+ Log.Info("Service {0} exited", ServiceName);
+ base.OnStop();
+ }
+ }
+}
diff --git a/contrib/windows-agent/WindowsAgent/WindowsServiceInstaller.cs b/contrib/windows-agent/WindowsAgent/WindowsServiceInstaller.cs
new file mode 100644
index 00000000..c737b28c
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/WindowsServiceInstaller.cs
@@ -0,0 +1,39 @@
+using System.ComponentModel;
+using System.Configuration.Install;
+using System.Linq;
+using System.Reflection;
+using System.ServiceProcess;
+
+namespace Mirantis.Murano.WindowsAgent
+{
+ [RunInstaller(true)]
+ public class WindowsServiceInstaller : Installer
+ {
+ public WindowsServiceInstaller()
+ {
+ var processInstaller = new ServiceProcessInstaller { Account = ServiceAccount.LocalSystem };
+ foreach (var type in Assembly.GetEntryAssembly().GetExportedTypes().Where(t => t.IsSubclassOf(typeof(ServiceBase))))
+ {
+ var nameAttribute = type.GetCustomAttributes(typeof (DisplayNameAttribute), false)
+ .Cast().FirstOrDefault();
+ if(nameAttribute == null) continue;
+ var serviceInstaller = new ServiceInstaller {
+ StartType = ServiceStartMode.Automatic,
+ ServiceName = nameAttribute.DisplayName,
+ DisplayName = nameAttribute.DisplayName
+ };
+ var descriptionAttribute = type.GetCustomAttributes(typeof(DescriptionAttribute), false)
+ .Cast().FirstOrDefault();
+ if(descriptionAttribute != null)
+ {
+ serviceInstaller.Description = descriptionAttribute.Description;
+ }
+
+ Installers.Add(serviceInstaller);
+ }
+
+ Installers.Add(processInstaller);
+
+ }
+ }
+}
diff --git a/contrib/windows-agent/WindowsAgent/packages.config b/contrib/windows-agent/WindowsAgent/packages.config
new file mode 100644
index 00000000..7aabef8e
--- /dev/null
+++ b/contrib/windows-agent/WindowsAgent/packages.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/contrib/windows-agent/packages/repositories.config b/contrib/windows-agent/packages/repositories.config
new file mode 100644
index 00000000..7753eee4
--- /dev/null
+++ b/contrib/windows-agent/packages/repositories.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file