Jeff Griffin

Wake on Lan with IronPython

Posted on April 14, 2011

Since introducing a custom built Media Center PC to my living room configuration back in 2005, the machine filling the role has also become a local storage server of sorts.  The music functions and storage requirements alone make it an appealing location for centralized storage.  Of course it's not always on, nor would we really want it to be, so we would need an easy way to wake it up from workstations, phones, and laptops on the LAN.

Enter Wake-on-Lan.  It wasn't difficult to figure this out. In fact my Xbox, which we were using as an extender in the bedroom, was already using it.  With that already working, there was no configuration to be done on the Media Center PC itself (I'll cover it anyway).  Writing a WOL.exe to form the "Magic Packet" and send it was easy, setting up shortcuts on my wife's and my own systems with the right command arguments to include the port and the Media Center's MAC address was even easier.

In my last post I discussed Windows scripting, and it seemed like something like this was a good candidate for an example.  Plus, back when I wrote that WOL.exe, it seemed a little heavy handed, but I still loved the simplicity of the code itself and wanted to share it.

Set up the Host

As I said, I didn't have to mess with this, but you will need to make sure that your host's network adapter is ready to receive a Wake-on-Lan. Go to device manager, open up your favorite network adapter, and make sure the setting to the right is enabled.

While you're at it, get the MAC address of the adapter.  You can do an "ipconfig /all" in the command prompt and it will be listed as "Physical Address".  Write this down for later.

Some systems will require that you modify settings in the BIOS to support Wake-on-Lan.  I'm not going to cover it, since BIOS menus vary, but this was another thing that I happily didn't need to do.

The Script
Pull out the MAC address you recorded from the host machine and place each hex digit in the byte array, WAKE_MAC_ADDRESS.

import clr
clr.AddReference('System')
clr.AddReference('System.Core')
from System import *
from System.Net.Sockets import *
from System.Net import *
from System.Linq import *

WAKE_MAC_ADDRESS = Array[Byte]([0x0A,0x0D,0x0D,0x04,0x0E,0x55])
PORT = 40000

#fill the first 6 bytes of the packet with 0xFF
magicPacket = Enumerable.Repeat[Byte](0xff, 6);

#repeat the MAC address 16 times
for i in range(16):
magicPacket = Enumerable.Concat(magicPacket, WAKE_MAC_ADDRESS)

#send a UDP boradcast of the magic packet
socket = Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
broadcastEndpoint = IPEndPoint(IPAddress.Broadcast, PORT)
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, True)
socket.SendTo(Enumerable.ToArray(magicPacket), broadcastEndpoint)
socket.Close()


Shortcut

What I did at this point was to make a shortcut called "Wake Emcee" (Emcee being the name of my Media Center PC), and pointed it to ScriptShell using the -a option so it silently executes the script and exits.  Shell32.dll has the perfect icon for this.  It will be the default location searched, if you click "Change Icon...".

ScriptShell: a Command Line host for Powershell/IronPython

Posted on April 13, 2011

I wanted to write about a tool project as soon as I could, because custom tooling is something that I enjoy doing.  Time and my mortgage tend to enforce that these excursions can be completed in a weekend or less, so this is a simple, yet effective solution for my Windows scripting needs.

Design Goals

  • Support two popular language options, Powershell and Python.
  • Target the 4.0 Framework.
  • Tool contains its own references, including scripting runtimes, for optimal redistribution.
  • Dynamically reference custom libraries.

Use Cases

  • Easily distributed unit/integration/regression testing of .NET libraries
  • Generalized .NET 4.0 platform automation, who needs batch files?
  • Can be set as the default binding for PS1 and PY files, or maintain your Powershell/ipy bindings and use GRIFFPS and GRIFFPY extensions.

Download ScriptShell 1.0.0.0
Source

Usage

Usage: ScriptShell [script-file-list] [-r reference-directory-list]
                   [-d scripts-directory-list] [-a]

Options:
 script-file-list              Run the specified script file(s) .
 -r reference-directory-list   Look for reference assemblies in this/these
                               location(s).
 -d scripts-directory-list     Run scripts from the specified
                               directory/directories.
 -a                            Automated mode.  This option will not prompt
                               for advancement. 
                               Note: Console prompts written into script
                               files will be unaffected.

 When specifying script files with the -s and -d options, the specified
 script files or runnable directory contents must be named with the
 following extensions:
                               *.py or *.griffpy for IronPython scripts
                               *.ps1 or *.griffps for PowerShell scripts";

Motivation

I wrote the first iteration of this tool out of frustration.  My client was experiencing issues regarding connection stability with a back-end service.  Reproducing the issue was problematic, and results varied by machine and locale.  It occurred to me that it might be nice to simplify his testing environment, and that of his support staff by giving him something without a UI, that would call directly into my Model code and would always act in a consistent, predictable way, regardless of who ran it.  Something similar to a unit/integration test platform might be nice, except the software requirements would be unwieldy.  A Powershell script seemed like a perfect option, until I realized it singularly targeted the 2.0 framework.  There were a few documented workarounds for this, but it was pretty apparent by now that I'd save time by writing up a Command Line Powershell host, targeting the 4.0 Framework, with references to my Model code.

Hosting Powershell

This was a relatively easy task, and is primarily just the tedium of tying all of the Powershell hooks to the command line.  This post by Mitch Denney was particularly helpful as a starting point.  Since my PSHost would need a UI, I needed to inherit PSHostUserInterface as well.  This is where the hooks are set into the command line.


public class ConsolePsHostUserInterface : PSHostUserInterface
{
	//other tedium

	//wire to Console
	public override string ReadLine()
	{
		return Console.ReadLine();
	}

	public override System.Security.SecureString ReadLineAsSecureString()
	{
		SecureString secret = new SecureString();
		ConsoleKeyInfo currentKey;
		while ((currentKey=Console.ReadKey(true)).Key != ConsoleKey.Enter)
		{
			if (currentKey.Key == ConsoleKey.Backspace)
			{
				if (secret.Length > 0)
				{
					secret.RemoveAt(secret.Length - 1);
					Console.Write(currentKey.KeyChar);
				}
			}
			else
			{
				secret.AppendChar(currentKey.KeyChar);
				Console.Write("*");
			}
		}
		Console.WriteLine();
		secret.MakeReadOnly();
		return secret;
	}

	public override void Write(string value)
	{
		Console.Write(value);
	}
}

public class ConsolePSHost : PSHost
{
	//more boringness

	//insert my console UI
	private PSHostUserInterface _ui = new ConsolePsHostUserInterface();
	public override PSHostUserInterface UI
	{
		get { return _ui; }
	}
}

All that's left to do is to add a lazily-instantiated property for the Powershell environment, console exception handling  and a nice calling convention for my application code.


public static class PowerShellRunner
{
	private static PowerShell _pws = null;
	private static PowerShell PowerShell
	{
		get
		{
			if (_pws == null)
			{
				_pws = PowerShell.Create();
                    		ConsolePSHost host = new ConsolePSHost();
                    		_pws.Runspace = RunspaceFactory.CreateRunspace(host);
                    		_pws.Runspace.Open();
			}
			return _pws;
		}
	}

	public static void Run(string file)
	{
		PowerShell.AddScript(File.ReadAllText(file));
		PowerShell.AddCommand("Out-Default");
		try { PowerShell.Invoke(); }
		catch (Exception e)
		{
			Console.Write(e.ToString());
			Console.WriteLine();
		}
	}
}

So I called into something similar to this from a command line application that searches a known subdirectory of the current location for Powershell scripts and it worked like a charm.  It's about now that I realize how much I don't like calling into .NET assemblies with Powershell.  Cmdlets are a nice shorthand for common operations, but who can remember all of them?  Here's a line of script I wrote to expand a log path with an environment variable:


$logName = [System.Environment]::ExpandEnvironmentVariables("%USERPROFILE%\ScriptShell\Test1.log")

I forget how to use that crazy bracket-and-double-colon syntax every time, and I end up having to Google it or look it up in another script.  Still, this accomplished what I set out to do, and I know there are plenty of Powershell lovers out there.

Hosting IronPython

It dawned on me later that not only is it easier to host the Dynamic Language Runtime, but my personal preference swings toward writing a script using Python's syntax.  Here's an example of the same action as above, written in Python:


logName = Environment.ExpandEnvironmentVariables("%USERPROFILE%\ScriptShell\Test1.log")

Here's what it took to get IronPython support:


public static class PythonRunner
{
	private static ScriptEngine _engine = null;
	private static ScriptEngine ScriptEngine
	{
		get
		{
			if (_engine == null)
			{
				_engine = IronPython.Hosting.Python.CreateEngine();
				_engine.Runtime.IO.SetOutput(Console.OpenStandardOutput(), Console.Out);
				_engine.Runtime.IO.SetErrorOutput(Console.OpenStandardOutput(), Console.Out);
			}
			return _engine;
		}
	}

	public static void Run(string file)
	{
		try { ScriptEngine.CreateScriptSourceFromFile(file).Execute(); }
		catch (Exception e)
		{
			Console.Write(e.ToString());
			Console.WriteLine();
		}
	}
}

...easy as a cliche.  Of course, in a sense, what I have done is rewritten ipy.exe, without the interactive mode (this might be a good follow-up), but in the fist pass, the easiest way to distribute this functionality was to directly reference my Model code and pass this out as an EXE to be dropped into the install directory of my client's software.  This implementation is a generalized implementation of the same concept, where any reference needed by a script is satisfied dynamically with a command line option at run-time.  Developers who do not need to redistribute their scripting environment may find that ipy.exe works just fine for them, the Python scripts will look the same in either case.

I had one major hiccup with using the IronPython Runtime for this tool, and it came when I attempted to pull the application out of my Release directory and only use the references I had embedded as resources.  The issue and workaround are adequately described here.   The workaround involves a modification, and custom build of the runtime.  Thanks to rodrigobarnes for that.

Download ScriptShell 1.0.0.0
Source