Construct 2 in WinForms

Applies To: Construct 2, CefSharp, VB.NET, WinForms, jsMessage

In my previous post, Embedded Chromium in WinForms, I walked you through creating a simple WinForms application that will load local html resources into an embedded Chromium browser using CefSharp. This article will build on that application to show you how to host Construct 2 games inside your WinForms application. Additionally, I will show you how to use my Construct 2 jsMessage plugin to communicate directly from .NET code to your running game.

Hosting a Construct 2 Game

For this article I will be using the jsMessageTest Basic game available on the jsMessage CodePlex site as an example download. You can use either the paid or free versions of Construct 2 to create the resources but you will need to install the jsMessage plugin. More details about this plugin and about the example game we’ll be using can be found in my posts, Introducing jsMessage for Construct 2 and jsMessage Basic Example.

You can also just use your own game or one of the many sample games that comes with Construct 2. However, you won’t be able to follow along with the Sending/Receiving messages section of this post without the jsMessage plugin.

Building the Game

Open the jsMessageTest Basic.capx in Construct 2. Choose File > Export project… In the dialog, choose HTML5 website and click Next:

ExportC2

In the Export Options dialog choose the location as a C2 folder inside your application’s directory (mine is bin/x64/debug/C2). You also need to uncheck the Minify script checkbox. I’m unsure of the reason but currently you will receive an error in the console if you attempt to load a game that has been minified using C2. I suspect this is an issue with the version of Construct 2 I am running but it could easily be CefSharp. Either way, uncheck the box for now and click Next:

C2ExportOptions

Choose Normal Style in the template options and click Export.

Loading the Game

Assuming you are using the same WinForms project created in my Embedded Chromium in WinForms post you can just switch the address in the browser constructor to local://C2/index.html

Go ahead and run the application. If the game shows up for you, fantastic!

Unfortunately, I get a blank screen when using the default settings. I’m able to fix this by disabling the GPU hardware acceleration.  This is a known issue with certain versions of Chromium when paired with specific drivers/hardware. If you have this issue, you can easily pass Chromium Command Line Switches using the CefSettings object in CefSharp. We can do this by adding this line right before we call the Cef.Initialize function in our Form constructor:

        settings.CefCommandLineArgs.Add("disable-gpu","1")

When you run the application it should look similar to the following:

C2inWinForms

This “game” doesn’t have much to offer without some additional plumbing (see below) but it is fully interactive. If you were to switch it out for a platformer game or something similar you would see that all the key presses, clicks, etc. are all passed just like you’d expect!

Sending Messages to Construct 2

The jsMessageTest Basic game was built with the jsMessage plugin. This plugin allows the game to respond to jQuery events and to trigger events of its own. You can find a lot more detail about how this works with this game in my post, jsMessage Basic Example.

It’s really pretty straightforward if you’re familiar with jQuery events so we won’t be spending much time on explaining it. Suffice it to say we are going to be injecting JavaScript in to our browser that will allow us to interact directly with the game from the code.

I’ve added a GroupBox labeled Send Messages and inside I’ve put a TextBox called txtMessageToSend and a Button called btnSendMessage. Here’s the code for the btnSendMessage Click EventHandler and the helper sub SendMessage:

    Private Sub btnSendMessage_Click(sender As Object, e As EventArgs) Handles btnSendMessage.Click
        If Not String.IsNullOrEmpty(txtMessageToSend.Text) Then
            SendMessage(txtMessageToSend.Text)
            txtMessageToSend.Text = String.Empty
        End If
    End Sub

    Private Sub SendMessage(Message As String)
        If browser IsNot Nothing Then
            addActivity(String.Format("MESSAGE: {0}", Message))
            browser.ExecuteScriptAsync(String.Format("$(document).trigger('CKjsMessageSend','{0}');", Message))
        End If
    End Sub

In the Click EventHandler (lines 49-54) we’re just making sure there is a message to send, calling the SendMessage sub and clearing the txtMessageToSend box.

The SendMessage sub is doing the actual interesting work. First, we verify the browser is setup. Then we use our addActivity sub to log the message. Finally, we call the ExecuteScriptAsync method which allows us to execute JavaScript directly on the page within our browser. This JavaScript triggers the CKjsMessageSend event with our message as the parameter (this is the format expected by the jsMessage plugin).

Run the application, type something in the box and click Send and you should have something like the following:

InitialMessageSend

Receiving Messages from Construct 2

Construct 2 can send messages via jQuery events using the jsMessage plugin. We can easily register a JavaScript function to be performed when that event is triggered. But how do we respond to that with .NET code?

CefSharp provides the ability to expose a .NET class to JavaScript. This is totally awesome. There are some limitations regarding the complexity of the objects and their return types, etc. all of which you can find on their project page. For our purposes, we just need a simple proxy object that can accept messages and route them.

Add another class to your project called MessageReceiver.vb and copy/paste the following code into it:

Public Class MessageReceiver

    Private logReceiver As Action(Of String)

    Public Sub New(LogReceiverAction As Action(Of String))
        logReceiver = LogReceiverAction
    End Sub

    Public Sub log(Message As String)
        logReceiver(String.Format("RECEIVED: {0}", Message))
    End Sub

End Class

This is not particularly exciting code but it should illustrate what is possible. It should also look somewhat familiar if you followed the steps to make our LogDialogHandler object in the last article. In our constructor (lines 5-7) we accept an Action(Of String) which we will use to handle our logging. We store this Action into our private logReceiver object (line 6) so that we can use it later.

There is just one method, log, which takes a string, adds “RECEIVED:” to the front of it and calls the logReceiver action. I’ve lowercased this method to match what will happen once exposed to JavaScript. CefSharp automatically changes methods and properties into JavaScript-casing (the first letter is downcased). I find it less confusing to just do that directly in the object.

Now we just need to register our object into our browser. We can do this once the browser is initialized using the RegisterJsObject method. Here is the line of code to do that in the Form constructor right after setting up our JsDialogHandler:

browser.RegisterJsObject("messageReceiver", New MessageReceiver(New Action(Of String)(AddressOf addActivity)))

The RegisterJsObject takes 2 parameters: The name we want to use in JavaScript for the object and the object itself. In our case we want it called messageReceiver (this will be a global object) and we just create a new instance of our MessageReceiver pointing the logReceiver Action to our addActivity method.

Go ahead and run the project and click the DevTools button. Switch to the console and start to type messageReceiver. You’ll find that Chrome’s autocomplete recognizes that there is a global messageReceiver object. If you call the messageReceiver.log function with a string you’ll see it show up in the Activity feed:

messageReceiver

Now we just need to tell jQuery to call this function when receiving a message from the Construct 2 game. We do this by using the ExecuteScriptAsync method we used earlier when sending messages.

However, we have to make sure the game is loaded before we insert the event handler or it won’t take effect. We can do this by taking advantage of the browser’s IsLoadingChanged event. Add the following line to your Form constructor right after our RegisterJsObject call:

AddHandler browser.IsLoadingChanged, AddressOf onBrowserIsLoadingChanged

So now let’s add the onBrowserIsLoadingChanged sub to our Form code:

    Private Sub onBrowserIsLoadingChanged(sender As Object, e As CefSharp.IsLoadingChangedEventArgs)
        If e.IsLoading = False Then
            browser.ExecuteScriptAsync("$(document).on('CKjsMessageReceive',function(e,m){messageReceiver.log(m);});")
            RemoveHandler browser.IsLoadingChanged, AddressOf onBrowserIsLoadingChanged
        End If
    End Sub

The IsLoadingChanged event provides us with a helpful event argument that tells us if the Browser is loading or not. We verify that it is no longer loading then inject our JS event handler and remove the .NET event handler from the IsLoadingChanged event (since we only need to call this once).

Run the application and type a message in the game textbox and click the jsMessage plugin icon (the turquoise speech bubble) and you’ll see that message come into the Activity feed:

C2Received

 

You now have all the basic plumbing in place to host a Construct 2 game directly in your WinForms application and to be able to send and receive messages directly from the game! This opens up a wide range of possible applications. I wrote all of this for an integrated project I’m working on, but I hope you find it helpful too!

Embedded Chromium in WinForms

Applies To: WinForms, VB.NET, CefSharp

Getting Chromium to show up inside a .NET WinForms application is relatively easy using CefSharp. CefSharp is an open source project which provides Embedded Chromium for .NET (WPF & WinForms). It’s a great way to get Chrome hosted inside of your .NET application.

You can get a simple browser up and running in 5 minutes – which I’ll show you first. There are some additional steps required to use local resources and to handle console messages and dialog prompts which I’ll also show you.

Embedding a Chromium Browser

There’s an easy example over on the CefSharp project page called the MinimalExample that will get you up and running quickly with a bunch of the basics. I’m going to walk you through some very quick steps to just get an embedded browser working in a WinForm.

Getting CefSharp

First, create a new WinForms project. I’m using VB.NET (but C# works great too, of course) and am targeting .NET 4.5. Once you’ve got a project, you’ll need to add the CefSharp binaries. This is done through NuGet and is really simple.

In Visual Studio choose PROJECT > Manage NuGet Packages… In the Package Manager window switch to Online and search for CefSharp. You’ll select CefSharp.WinForms (My version is 37.0.0 but the package should always have the latest stable release) and choose Install:

CefSharpWinFormsNuGet

This will take just a few seconds while it also adds all of the dependent packages. In the end you’ll have 4 packages (CefSharp.WinForms, CefSharp.Common, cef.redist.x64 and cef.redist.x86).

Initial Build

CefSharp doesn’t support the default AnyCPU build configuration. You’ll need to choose BUILDConfiguration Manager… Then change the Active solution platform to either x64 or x86 (just choose new and it’ll walk you through it). Here’s what my Debug configuration looks like:

ConfigurationManager

Go ahead and build the project to make sure there are no reference errors.

Adding the Browser

Open your form and slap a Panel control on there (I’ve named mine panBrowser and set it’s Dock property to Fill). This isn’t required, but it certainly makes it easier to move around when changing your form later.

Switch to the Form’s code and go to the New sub (Constructor in C#). Here’s the code:

Imports CefSharp.WinForms
Imports CefSharp

Public Class Form1

    Private WithEvents browser As ChromiumWebBrowser

    Public Sub New()
        InitializeComponent()

        Dim settings As New CefSettings()
        CefSharp.Cef.Initialize(settings)

        browser = New ChromiumWebBrowser("http://thechriskent.com") With {
            .Dock = DockStyle.Fill
        }
        panBrowser.Controls.Add(browser)

    End Sub
End Class

Be sure to make the appropriate references above (lines 1-2). In this code, we have a ChromiumWebBrowser object (line 6) that we create with a default address and a dock style of Fill (lines 14-15). We then add that control to the Panel we added above. The only other thing we need to do is to call the Cef.Initialize function (line 12). We’re just passing default settings for now (line 11). Run it and you should see something similar to this:

WinFormsBasicBrowser

Congratulations, you’ve got Chrome in your Form!

Loading From Local Resources

In a previous article, Use Local Files in CefSharp, I showed you how to make the necessary objects to register a CefCustomScheme that would load resources from the local file system. I also posted a similar article, Use Embedded Resources in CefSharp, to demonstrate how to load files directly from your project’s manifest. Those articles will give you more detail about how to do this. If you’re following along in C# just head over to that article and copy the objects. Otherwise, here they are in VB.NET:

LocalSchemeHandler

Add a new Class to your project called LocalSchemeHandler.vb and copy/paste the following:

Imports CefSharp
Imports System.IO
Public Class LocalSchemeHandler
    Implements ISchemeHandler

    Public Function ProcessRequestAsync(request As IRequest, response As ISchemeHandlerResponse, requestCompletedCallback As OnRequestCompletedHandler) As Boolean Implements ISchemeHandler.ProcessRequestAsync
        Dim u As New Uri(request.Url)
        Dim filepath As String = u.Authority & u.AbsolutePath

        If File.Exists(filepath) Then
            Dim bytes As Byte() = File.ReadAllBytes(filepath)
            response.ResponseStream = New MemoryStream(bytes)
            Select Case Path.GetExtension(filepath)
                Case ".html"
                    response.MimeType = "text/html"
                Case ".js"
                    response.MimeType = "text/javascript"
                Case ".png"
                    response.MimeType = "image/png"
                Case ".appcache" OrElse ".manifest"
                    response.MimeType = "text/cache-manifest"
                Case Else
                    response.MimeType = "application/octet-stream"
            End Select
            requestCompletedCallback()
            Return True
        End If
        Return False
    End Function

End Class
LocalSchemeHandlerFactory

Add another new Class to your project called LocalSchemeHandlerFactory.vb and copy/paste the following:

Imports CefSharp
Public Class LocalSchemeHandlerFactory
    Implements ISchemeHandlerFactory

    Public Function Create() As ISchemeHandler Implements ISchemeHandlerFactory.Create
        Return New LocalSchemeHandler
    End Function

    Public Shared ReadOnly Property SchemeName() As String
        Get
            Return "local"
        End Get
    End Property

End Class
Registering the Scheme

To tell the browser to use the LocalSchemeHandler we just need to adjust our settings object before the Cef.Initalize function from our Form constructor above:

        Dim settings As New CefSettings()
        settings.RegisterScheme(New CefCustomScheme() With {
                                .SchemeName = LocalSchemeHandlerFactory.SchemeName,
                                .SchemeHandlerFactory = New LocalSchemeHandlerFactory
                                })
        CefSharp.Cef.Initialize(settings)
Proving it Works

To make sure everything is working we’ll need to have an actual local resource to load. Open your application’s directory (mine is bin/x64/debug) and create a folder called web. Cut and paste the following into a text file and save it as index.html in that web folder:

<!DOCTYPE html>
<html>
    <body>
        <h1>My First Heading</h1>
        <p>My first paragraph.</p>
        <img src="images/truck.png"/>
        <script type="text/javascript">
            console.log("Hello from the console!");
            //alert("Hello from a dialog!");
        </script>
    </body>
</html>

Create an images folder inside the web folder and save this image as truck.png in there:

truck

None of these files need to be added to your project, they just need to be in the directory with your executable. Now if you switch the address from the browser initialization code above to local://web/index.html and run the project you should see something like this:

LocalResource

Mapping the Console

You might have noticed a <script> tag in our index.html above with a call to the console. CefSharp provides us with an easy event, ConsoleMessage, that allows us to grab those messages and do whatever we want with them. In our case we just want to display those messages in a TextBox.

I’ve added a Groupbox below the panBrowser Panel control from earlier and put a TextBox control called txtActivity inside it. I’ve set the txtActivity properties like so: Dock=Fill, Multiline=True and ScrollBars=Both. Now we just need to add the following code to our Form code right after the constructor:

    Private Sub onBrowserConsoleMessage(sender As Object, e As CefSharp.ConsoleMessageEventArgs) Handles browser.ConsoleMessage
        If e.Line > 0 Then
            addActivity(String.Format("CONSOLE: {0} ({1}|{2})", e.Message, e.Source, e.Line))
        Else
            addActivity(String.Format("CONSOLE: {0}", e.Message))
        End If
    End Sub

    Private Sub addActivity(Message As String)
        If txtActivity.InvokeRequired Then
            txtActivity.Invoke(New Action(Of String)(AddressOf addActivity), Message)
        Else
            txtActivity.AppendText(Message & vbCrLf)
        End If
    End Sub

We’ve added a handler for the ConsoleMessage event (lines 25-31). The ConsoleMessageEventArgs object provides us with three useful properties: Message, Source and Line. We always receive the Message and depending on how the call to the console was made we may receive information about the Source. When the Line is greater than 0 we output the Message and the Source/Line information, otherwise it’s just the Message.

We’ve also added a helper sub, addActivity, to take care of handling displaying the output. This allows us to easily change our handling in the future but it also simplifies thread considerations. Our embedded browser is multithreaded and the ConsoleMessage event generally doesn’t fire on the main thread. This is the reason for the InvokeRequired/Invoke code in lines 34-36. The actual display happens in line 37 where we use the AppendText method to ensure our textbox scrolls appropriately. Run the project and should see something similar to this:

ConsoleActivity

Handling Dialogs

What about dialog messages? If you uncomment the alert command from the index.html above and run the application you’ll see that in WinForms this gets automatically handled with a MsgBox:

dialogMsgBox

In our application we’d like to redirect these to our Activity feed as well. In CefSharp, you override the default handling of dialogs (alerts, confirmations and prompts) by implementing an IJsDialogHandler object.

In our case we just want everything noted in the activity feed and ignored. To do this, add a class to your project called LogDialogHandler.vb and copy/paste the following code into it:

Imports CefSharp
Public Class LogDialogHandler
    Implements IJsDialogHandler

    Private logReceiver As Action(Of String)

    Public Sub New(LogReceiverAction As Action(Of String))
        logReceiver = LogReceiverAction
    End Sub

    Public Function OnJSAlert(browser As IWebBrowser, url As String, message As String) As Boolean Implements IJsDialogHandler.OnJSAlert
        logReceiver(String.Format("ALERT: {0}", message))
        Return True
    End Function

    Public Function OnJSConfirm(browser As IWebBrowser, url As String, message As String, ByRef retval As Boolean) As Boolean Implements IJsDialogHandler.OnJSConfirm
        logReceiver(String.Format("CONFIRM: {0}", message))
        retval = True
        Return True
    End Function

    Public Function OnJSPrompt(browser As IWebBrowser, url As String, message As String, defaultValue As String, ByRef retval As Boolean, ByRef result As String) As Boolean Implements IJsDialogHandler.OnJSPrompt
        logReceiver(String.Format("PROMPT: {0} ({1})", message, defaultValue))
        result = defaultValue
        retval = True
        Return True
    End Function
End Class

The IJsDialogHandler interface requires us to implement the OnJSAlert, OnJSConfirm and OnJSPrompt functions. We’ve also added a construtor that takes an Action(Of String) which we will use to handle our logging. We store this Action into our private logReceiver object (line 8) so that we can use it later. Then in our implementation of the functions we just call logReceiver with an informative message regarding what’s occurred. In each case we return True to indicate that the alert, etc. has been handled.

To use our new object we just add the following line to the end of our Form constructor:

browser.JsDialogHandler = New LogDialogHandler(New Action(Of String)(AddressOf addActivity))

This allows us to direct any message from our LogDialogHandler to our txtActivity box just like our console messages. Now when we run the application we should see something like this:
AlertActivity

DevTools

Anyone that’s used Chrome for web development is very familiar with the F-12 DevTools and knows they can be invaluable in tracking down issues. Wouldn’t it be nice to have those when working within your .NET application? With CefSharp, it’s super easy. I’ve added a simple button underneath my Activity GroupBox called btnDevTools. Here’s the Click EventHandler:

    Private Sub btnDevTools_Click(sender As Object, e As EventArgs) Handles btnDevTools.Click
        browser.ShowDevTools()
    End Sub

That’s it! Now when you push the button you get the full power of the Chrome DevTools all while running in your form:

DevTools

Conclusion

Obviously, there is a lot you can do with this and there are many features we haven’t even begun to demonstrate, but this should get you started. For me the goal has been to get a Construct 2 game running in a .NET application. In my next post I’ll show you exactly that.

jsMessage Basic Example

Applies To: Construct 2, jQuery, jsMessage

In my last post, Introducing jsMessage for Construct 2, I gave a brief overview of my C2 Plugin jsMessage. jsMessage enables sending and receiving messages in Construct 2 through jQuery events. You can read more in that post, but the basic idea is the ability to communicate to a Construct 2 game through the Browser. The license is free for everybody and attribution isn’t required.

In this post, I’m going to walk through the jsMessageTest Basic game to show you exactly how it works. You can also download it over on CodePlex if you’d like to follow along.

“Game” Overview

When you first run the project not much is going to happen. You’ll see a big red message that says “Nothing Yet…” – once you’ve successfully sent a message the contents will be displayed here.

InitialScreen

This is a very simple game project with just a few assets and only a small set of standard objects (Touch, Sprite, Browser, Particles, Text and Text Box). The only custom object is the jsMessage object. This was added to the project like any other object and can be found in the Web section (assuming you’ve installed it):

jsMessageInsertNewObject

Receiving Messages in Construct 2

The Event Sheet

jsMessage provides 2 conditions for receiving messages. The first one, Message Received, fires every time a message is received. In our game we are using it to set the text of the txtRecevied object (the big red text). We are also outputting additional information to the console. This helps illustrate several of jsMessage’s expressions but is not something you’d normally do outside of debugging. Here’s what this section of the Event Sheet looks like:

EventSheet-MessageReceived

Using the Log in console action of the built-in Browser object, we output information about the received message. The jsMessage.MessageRaw expression provides the full message string. The jsMessage.Command expression provides only the first part of the message before any values (known as the command). The jsMessage.ValueCount provides the total count of values passed (additional strings after the command separated by the Value Separator Property).

Finally, there’s a For Loop that outputs each of the message’s values (if there were any) by using the jsMessage.Value() expression.

Client-Side

Let’s give it a go. For all of the client-side examples we’re just going to type the jQuery commands directly into the console. So go ahead and run the project and open the Dev Tools (Just hit F-12 in Chrome) and switch to the console. Type the following:

$(document).trigger('CKjsMessageSend','Hello World');

Once you hit enter, your screen should look like this:

HelloWorld

You can see the txtReceived object had its text set to the message and the console has all the log messages we expected (Raw & Command are equal in this case and the Value Count is 0).

Now try sending this:

$(document).trigger('CKjsMessageSend','DoThing|Turds|Sunshine');

DoThing

You’ll see the txtReceived object gets the full message just like before, but if you look in the console you’ll see some differences. We can now see our Command is DoThing and that we have 2 values: Value 0 is Turds and Value 1 is Sunshine. Of course, we are assuming the use of the default separator (The separator is customizable so it’s always a good idea to use the Separator Events to determine what that is before sending/receiving messages on the client).

Responding to Commands in Construct 2

The Event Sheet

Another condition provided by jsMessage is Command Received. This condition lets you specify the command to listen for. This is what we’re doing in the Turtle section of the Event Sheet:

TurtleCommand

We are listening for the command, “Turtle”. When it’s received we move a turtle sprite across the screen using a Bullet behavior (There are also a couple of conditions to reset the turtle once it leaves the screen).

Client-Side

Here’s how we trigger this command from the console:

$(document).trigger('CKjsMessageSend','Turtle');

Turtle

Look at that cute turtle! LOOK AT IT!

You can see everything works just like any standard condition (Note that the console also provides us all the extra information because both the Command “Turtle” Received and Message Received conditions are firing).

Responding to Commands with Values in Construct 2

The Event Sheet

Commands are just messages which means they can also have attached values. This can be seen in the Explosions section of the Event Sheet:

ExplosionsCommand

We are listening for the command, “Explode”, but we’ve added some additional conditions to ensure that there is an included value (jsMessage.ValueCount = 1) and that that value is an integer greater than 0 (int(jsMessage.Value(0)) > 0).

Once the above conditions are met,we use a For Loop to create the number of explosions (particle objects) as specified by the passed value (with a max of 10 cause let’s not get crazy!).

Client-Side

Here’s how we trigger this command from the console:

$(document).trigger('CKjsMessageSend','Explode|7');

Explode

GLORIOUS EXPLOSIONS!

Sending Messages from Construct 2

The Event Sheet

jsMessage provides a single Action, Send Message, that is really easy to use. You just provide the message as a parameter and it’ll take care of it:

SendEventSheet

All we’re doing above is treating the icon sprite like a button by responding to a tap (or click). We flash the button to give some feedback to the user that they tapped it and then call the Send Message action with the text of the txtboxSend object. Of course, nothing is going to happen if nobody is listening on the other side…

Client-Side

To receive messages from Construct 2 you will need to register to respond to the appropriate jQuery event. Here’s an extremely simple response that just writes the sent message out to the console:

$(document).on('CKjsMessageReceive',function(e,m){console.log(m);});

To test, just write something in the text box and click the button:

SendMessage

 

Sending Messages with Values from Construct 2

The Event Sheet

To send messages with values from your game you’ll build your messages using the same format as above. You can see an example of this in the Responding to Requests section of the Event Sheet:

RespondingToRequests

 

When we get the command, “FPS?”, we use the Send Message action to send a custom message built by concatenating a command, “FPS”, the separator using the jsMessage.Separator expression and the C2 value, fps. We could have just typed the default separator but since this is a customizable property it’s always better to use the jsMessage.Separator expression.

Client-Side

To test this one, we need a slightly more elaborate response function:

$(document).on('CKjsMessageReceive',function(e,m){console.log('Client-Side Message Received!');var parts=m.split('|');console.log('Command Received: '+parts[0]);for(var v=1; v<parts.length;v++){console.log('Value Received: '+parts[v]);}});

I’ve kept it to one line above so that it can be easily pasted in the console, but here’s what it looks like where it’s a little more readable:

$(document).on('CKjsMessageReceive',function(e,m){
    console.log('Client-Side Message Received!');
    var parts = m.split('|');
    console.log('Command Received: ' + parts[0]);
    for(var v = 1; v < parts.length; v++){
        console.log('Value Received: ' + parts[v]);
    }
});

SendMessagesWithValuesWhen we send the “FPS?” message to the game it responds with the fps information. Our client-side response just outputs the C2 message right back to the console. I’ve hard-coded the default separator in this example, but you’ll want to use the Separator Events beforehand to ensure you know what the separator is before receiving/sending messages from the client.

 

Be sure to check out the full documentation for more details. Stay tuned for an upcoming post where I’ll show you an actual use case for this plugin. WOWEE!

Introducing jsMessage for Construct 2

Applies To: Construct 2, jQuery

jsMessage is a Construct 2 plugin that enables sending and receiving messages through jQuery events. I’ve just released it over on CodePlex where you can download it and a sample game to show you how to use it. You can use it in the free edition as well as all paid editions. The license is totally open so feel free to use it in your commercial or personal projects, etc. No attribution necessary (although always appreciated).

BasicTest

You can find out how to install it by checking out the documentation on CodePlex (It’s a c2addon, so just drag and drop).

What

jsMessage is a cool little plugin that adds 2 conditions (Message Received, Command Received), 1 Action (Send Message), 5 Expressions (Message Raw, ValueCount, Command, Value, Separator) and 1 Property (Value Separator).

You can use these to respond to external messages coming through the browser.

Why

There are several other plugins that allow network communication and generally this is the way you’re going to want to go. If you are trying to have games talk to each other or download things, etc. – this is not the plugin for you. The only way to communicate to the game using this plugin is to trigger jQuery events and to register to receive them as well.

I had a specific need to communicate to a running game in a browser I control. I will be demonstrating this technique in an upcoming post and hopefully it will make more sense then. However, there are lots of other uses and I’m excited to see what other people end up using it for.

How

There is some more in-depth documentation available on the CodePlex site and I’ll be posting an elaborate walkthough using the basic example game in an upcoming post. In the meantime, here’s an overview.

To communicate to a running game you can send messages by triggering the CKjsMessageSend event. Here’s a one-liner perfect for the console window:

$(document).trigger('CKjsMessageSend','SomeCommand');

This will trigger both the Message Received and Command Received conditions in your game. Command Received allows you to respond to a specific phrase. Message Received is more general and you’ll have to do some comparisons to see if it was the message you were looking for.

You can also send values by using a delimiter. The default delimiter is the pipe | but this can be changed as a game setting. To find out what the separator has been set to you can use the CKjsMessageSeparatorQ and CKjsMessageSeparatorA events. It might look something like the following:

$(document).ready(function(){
    var jsSeparator = '';

    //Prepare to respond to the Separator Answer
    $(document).on('CKjsMessageSeparatorA',function(e,m){
        jsSeparator = m;

        //Send a message with values
        $(document).trigger('CKjsMessageSend','MyCommand' + jsSeparator + 'Value1' + jsSeparator + 'Value2');
    });

    //Ask the game what the Separator is
    $(document).trigger('CKjsMessageSeparatorQ','');
});

In other words, register to respond to the CKjsMessageSeparatorA event and then trigger the CKjsMessageSeparatorQ event to have the game respond.

Within the Message Received or Command Received condition you can get a count of the values sent with the jsMessage.ValueCount expression and then request those values using the jsMessage.Value(0) expression. There are also expressions to get the raw message (jsMessage.MessageRaw), just the command (jsMessage.Command) and even the configured separator (jsMessage.Separator).

The game can also send messages using the Send Message action. Here’s a quick example of how to register to receive these messages:

$(document).on('CKjsMessageReceive',function(e,m){
    console.log(m);
});

This will simply print out whatever message was sent directly to the console.

 

That’s the basic overview of the plugin. If you have a need for this kind of interaction then go download it and check out the example “game” (it’s free!). In my next post we’ll make this a little clearer by walking through the example game in detail.

Use Embedded Resources in CefSharp

Applies to: CefSharp, C#

In my last post, Use Local Files in CefSharp, I showed you how to create a CefCustomScheme to pull web files directly from the file system. This post will follow along the same lines except this time I’ll show you how to pull web files directly from your assembly manifest (Embedded Resources). In practical terms, I’m using both. You just call the RegisterScheme method (see below) for each CefCustomScheme you want to use. This is especially helpful during development so that I can quickly tweak things in the file system and then when things are ready to include in my larger project I include everything in the project and embed them. This keeps my files from being browsable/editable outside of my application.

Intro

CefSharp is an open source project which provides Embedded Chromium for .NET (WPF & WinForms). It’s a great way to get Chrome hosted inside of your .NET application. Sometimes you want to load sites/files that aren’t hosted on the web. The easiest way to do that is to provide a custom scheme. You do this by creating an instance of CefSettings and using the RegisterScheme method with a CefCustomScheme wrapper object which requires an implementation of the ISchemeHandlerFactory which will in turn require an implementation of the ISchemeHandler. If you didn’t read my last post about doing this from the file system (or even if you did) you might be a little confused. Fortunately, it isn’t near as complicated as it seems.

Quick Note: This isn’t a tutorial on how to get CefSharp working or integrated into your product. You’ll want to install the packages using NuGet and go to the CefSharp project page for details.

ISchemeHandler

SchemeHandler objects process custom scheme-based requests asynchronously. In other words, you can provide a URL in the form customscheme://folder/yourfile.html”. The SchemeHandler object takes the request and gives you a chance to provide a custom response. In our case, we want to take anything with the scheme resource and pull it from our assembly’s manifest. Here’s what that looks like:

using CefSharp;
using System.IO;
using System.Reflection;
public class ResourceSchemeHandler : ISchemeHandler
{
    public bool ProcessRequestAsync(IRequest request, ISchemeHandlerResponse response, OnRequestCompletedHandler requestCompletedCallback)
    {
        Uri u = new Uri(request.Url);
        String file = u.Authority + u.AbsolutePath;

        Assembly ass = Assembly.GetExecutingAssembly();
        String resourcePath = ass.GetName().Name + "." + file.Replace("/", ".");

        if (ass.GetManifestResourceInfo(resourcePath) != null)
        {
            response.ResponseStream = ass.GetManifestResourceStream(resourcePath);
            switch (Path.GetExtension(file))
            {
                case ".html":
                    response.MimeType = "text/html";
                    break;
                case ".js":
                    response.MimeType = "text/javascript";
                    break;
                case ".png":
                    response.MimeType = "image/png";
                    break;
                case ".appcache":
                case ".manifest":
                    response.MimeType = "text/cache-manifest";
                    break;
                default:
                    response.MimeType = "application/octet-stream";
                    break;
            }
            requestCompletedCallback();
            return true;
        }
        return false;
    }
}

I’ve named my implementation ResourceSchemeHandler. You can see we are implementing the ISchemeHandler interface in line 4 (If this isn’t recognized be sure to include the using statements in lines 1-3 above). This interface only requires a single method, ProcessRequestAsync. Our job is to translate the request into a response stream, call requestCompletedCallback and indicate if we handled the request or not (return true or false).

The first thing we do is translate the request URL into a file path (lines 8-9). Embedded Resources are stored in the form of AppName.Namespace(s).File.Extension. So we need to translate our file path to our resource path. We do this in lines 11-12. First we grab the current assembly (ass, hehe) because we’re going to need it anyway. Next we build our resource path by slapping our AppName (ass.GetName()) followed by a period onto our file path where we replace all the forward slashes with periods.

This allows us to create a folder inside our project (which will get mapped to a namespace in C#) and put our files in there. So if we were to use the address “resource://web/index.html” the file path will be “web/index.html” and the resource path (if our application is named C2Player) will be “C2Player.web.index.html”.

If the resource doesn’t exist (no info returned), there isn’t any way for us to handle the request so we return false (line 39). Otherwise, we set the response.ResponseStream directly to the ManifestResourceStream (line 16). We then guess the Mime Type based on the file’s extension (lines 17-35). This is a pretty limited list but it was all I needed – Just expand as necessary. If you are using both this SchemeHandler and the LocalSchemeHandler created in my last post, you should refactor this to a common function.

Once we’ve got everything we need, we call the requestCompletedCallback and return true to indicate we have handled the request.

ISchemeHandlerFactory

SchemeHandlerFactory objects create the appropriate SchemeHandler objects. I’ve also added a convenience property to help out during registration. Here’s mine:

using CefSharp;
class ResourceSchemeHandlerFactory : ISchemeHandlerFactory
{
    public ISchemeHandler Create()
    {
        return new ResourceSchemeHandler();
    }

    public static string SchemeName { get { return "resource"; } }
}

This one is called ResourceSchemeHandlerFactory (wowzers!) and you can see we are implementing the ISchemeHandlerFactory interface in line 2 (If this isn’t recognized be sure to include the using statement in line 1). This interface also only requires a single method, Create. All we have to do is return a new instance of our custom SchemeHandler (line 6).

The SchemeName property in line 9 is just there to make things easier during registration.

Registering Your Custom Scheme

Now that we have our custom SchemeHandler and the corresponding Factory, how do we get the Chromium web browser controls to take advantage of them? This has to be done before things are initialized (before the controls are loaded). In WPF this can usually be done in your ViewModel’s constructor and in WinForms within the Form Load event. Here’s what it looks like:

CefSettings settings = new CefSettings();
settings.RegisterScheme(new CefCustomScheme()
    {
        SchemeName = ResourceSchemeHandlerFactory.SchemeName,
        SchemeHandlerFactory = new ResourceSchemeHandlerFactory()
    });
Cef.Initialize(settings);

You’ll create a new instance of a CefSettings object (line 1) and call the RegisterScheme method which takes a CefCustomScheme object (lines 2-6). the CefCustomScheme object needs to have its SchemeName set to whatever you are going to be using in the URLs to distinguish your custom scheme (we are using resource) and its SchemeHandlerFactory should be set to a new instance of your custom SchemeHandlerFactory. Then call the Cef.Intialize with the settings object and all the plumbing is hooked up. Again, if you want to use additional custom schemes (such as our LocalSchemeHandler) just add another settings.RegisterScheme call before the Cef.Initialize call.

Giving it a go

In my solution I have created a folder called web. Inside the folder is an html file (index.html) and an images folder with a single image (truck.png). Both the html file and the image have their Build Action set to Embedded Resource. Resource paths are case sensitive. For whatever reason, the request will always come back all lower case (regardless of your address casing) so you’ll need to ensure all your files and folders are lower case as well. Here’s what my test files look like in Solution Explorer:

EmbeddedResources

The html page is very simple:

<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<img src="images/truck.png"/>
</body>
</html>

Now if I set my ChromiumWebBrowser (WPF) or WebView (WinForms) Address property to resource://web/index.html I get the following:

LocalSchemeHandler

You can see the web page loaded both the HTML and the linked image from the assembly’s manifest. There’s even a couple of dummy buttons underneath the truck to show you that it’s just a standard WPF window.

Since I’m using WPF I also had the option of setting the Build Action to Resource and rewriting a small part of the SchemeHandler to pull from there. I chose Embedded Resource since it’s more widely applicable. Between these two posts hopefully you see that writing a CefCustomScheme is actually really straightforward. Have fun!

Use Local Files in CefSharp

Applies to: CefSharp, C#

CefSharp is an open source project which provides Embedded Chromium for .NET (WPF & WinForms). It’s a great way to get Chrome hosted inside of your .NET application. Recently I had the need of loading web files directly from the file system. The easiest way to do that is to provide a custom scheme. You do this by creating an instance of CefSettings and using the RegisterScheme method with a CefCustomScheme wrapper object which requires an implementation of the ISchemeHandlerFactory which will in turn require an implementation of the ISchemeHandler. Got it?! It’s actually much less confusing than it seems. It will make much more sense in a moment.

Quick Note: This isn’t a tutorial on how to get CefSharp working or integrated into your product. You’ll want to install the packages using NuGet and go to the CefSharp project page for details.

ISchemeHandler

SchemeHandler objects process custom scheme-based requests asynchronously. In other words, you can provide a URL in the form customscheme://folder/yourfile.html”. The SchemeHandler object takes the request and gives you a chance to provide a custom response. In our case, we want to take anything with the scheme local and pull it from the file system. Here’s what that looks like:

using CefSharp;
using System.IO;
public class LocalSchemeHandler : ISchemeHandler
{
    public bool ProcessRequestAsync(IRequest request, ISchemeHandlerResponse response, OnRequestCompletedHandler requestCompletedCallback)
    {
        Uri u = new Uri(request.Url);
        String file = u.Authority + u.AbsolutePath;

        if (File.Exists(file))
        {
            Byte[] bytes = File.ReadAllBytes(file);
            response.ResponseStream = new MemoryStream(bytes);
            switch (Path.GetExtension(file))
            {
                case ".html":
                    response.MimeType = "text/html";
                    break;
                case ".js":
                    response.MimeType = "text/javascript";
                    break;
                case ".png":
                    response.MimeType = "image/png";
                    break;
                case ".appcache":
                case ".manifest":
                    response.MimeType = "text/cache-manifest";
                    break;
                default:
                    response.MimeType = "application/octet-stream";
                    break;
            }
            requestCompletedCallback();
            return true;
        }
        return false;
    }
}

I’ve named my implementation LocalSchemeHandler because I’m creative like that. You can see we are implementing the ISchemeHandler interface in line 3 (If this isn’t recognized be sure to include the using statements in lines 1 and 2 above). This interface only requires a single method, ProcessRequestAsync. Our job is to translate the request into a response stream, call requestCompletedCallback and indicate if we handled the request or not (return true or false).

The first thing we do is translate the request URL into a local file path (lines 7-8). If the file doesn’t exist, there isn’t any way for us to handle the request so we return false (line 36). Otherwise, we set the response.ResponseStream to a MemoryStream from the file’s bytes (lines 12-13). We then guess the Mime Type based on the file’s extension (lines 14-32). This is a pretty limited list but it was all I needed – Just expand as necessary.

Once we’ve got everything we need, we call the requestCompletedCallback and return true to indicate we have handled the request.

ISchemeHandlerFactory

SchemeHandlerFactory objects create the appropriate SchemeHandler objects. I’ve also added a convenience property to help out during registration. Here’s mine:

using CefSharp;
public class LocalSchemeHandlerFactory : ISchemeHandlerFactory
{
    public ISchemeHandler Create()
    {
        return new LocalSchemeHandler();
    }

    public static string SchemeName { get { return "local"; } }
}

This one is called LocalSchemeHandlerFactory (surprise!) and you can see we are implementing the ISchemeHandlerFactory interface in line 2 (If this isn’t recognized be sure to include the using statement in line 1). This interface also only requires a single method, Create. All we have to do is return a new instance of our custom SchemeHandler (line 6).

The SchemeName property in line 9 is just there to make things easier during registration.

Registering Your Custom Scheme

Now that we have our custom SchemeHandler and the corresponding Factory, how do we get the Chromium web browser controls to take advantage of them? This has to be done before things are initialized (before the controls are loaded). In WPF this can usually be done in your ViewModel’s constructor and in WinForms within the Form Load event. Here’s what it looks like:

CefSettings settings = new CefSettings();
settings.RegisterScheme(new CefCustomScheme()
    {
        SchemeName = LocalSchemeHandlerFactory.SchemeName,
        SchemeHandlerFactory = new LocalSchemeHandlerFactory()
    });
Cef.Initialize(settings);

You’ll create a new instance of a CefSettings object (line 1) and call the RegisterScheme method which takes a CefCustomScheme object (lines 2-6). the CefCustomScheme object needs to have its SchemeName set to whatever you are going to be using in the URLs to distinguish your custom scheme (we are using local) and its SchemeHandlerFactory should be set to a new instance of your custom SchemeHandlerFactory. Then call the Cef.Intialize with the settings object and all the plumbing is hooked up.

Giving it a go

In my application’s directory (bin/x64/debug) I have created a folder called web. Inside the folder is an html file (index.html) and an images folder with a single image (truck.png). The html page is very simple:

<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<img src="images/truck.png"/>
</body>
</html>

Now if I set my ChromiumWebBrowser (WPF) or WebView (WinForms) Address property to local://web/index.html I get the following:

LocalSchemeHandler

You can see the web page loaded both the HTML and the linked image from the file system. There’s even a couple of dummy buttons underneath the truck to show you that it’s just a standard WPF window. This is all you need for simple web files stored locally. In some advanced cases you will need to adjust the BrowserSettings for your control to adjust permission levels (specifically FileAccessFromFileUrlsAllowed and UniversalAccessFromFileUrlsAllowed).

Intercepting SharePoint Email and Testing Outgoing E-Mail Settings

Applies To: SharePoint

I’m going to show you how to temporarily reroute SharePoint’s outgoing email to your desktop where you can either ignore it or forward it as necessary. There are a variety of legitimate reasons to do this. Generally I’ve used this when working in a small development or testing environment where email isn’t setup and it would be a huge hassle. However, I’ve also done this to test changes that involve email blasts (like false alarms on mysite deletions or screwy workflow debugging). As with anything, use some common sense about how and when to use this.

To run a dummy SMTP server on your desktop you’ll want to use smtp4dev by RNWood. This is a great little program that sits in the system tray and intercepts messages sent to it. You can then view, save or even forward them (from your account) and more. I’ve used it for years for all sorts of projects and I highly recommend it.

Configuring Outgoing Email

Generally your outgoing email settings in SharePoint were configured as part of your initial farm setup. To adjust these settings you’ll need to head to Central Administration. In SharePoint 2010 you’ll want to click on System Settings then choose Configure outgoing e-mail settings under E-Mail and Text Messages (SMS):

CentralAdmin

There’s only one thing we need to adjust here and that’s the Outbound SMTP server. If this is just a temporary change, be sure to write down the current value somewhere so you can set it back. Just put the name of your desktop here (Depending on your environment you may need to use your IP Address or whatever DNS entry points to where you are running smtp4dev). If you haven’t already configured your From address and Reply-to address go ahead and do that. you can just make them up (they don’t have to correspond to an actual mailbox, they just need to be email addresses). Your settings should look something like this:

MailSettings

Once you hit okay you will start receiving emails in smtp4dev (So be sure it’s running). You can also make these changes using Stsadm and can apply them to individual web applications instead of the whole farm like shown above. See this article for more details: Configure outgoing e-mail.

Sending a Test Email from SharePoint

So how do you know it’s working? Unless this is a large production farm with lots of alerts and other things flying off all the time you may not see anything at all. You can of course setup alerts on some list and then trigger events that would send the alert, but that can be a big hassle. Fortunately, you can have SharePoint send you an email through PowerShell.

Just copy and paste the script below and save as TestEmail.ps1:

$sd = New-Object System.Collections.Specialized.StringDictionary
$sd.Add("to","you@somedomain.com")
$sd.Add("from","spadmin@somedomain.com")
$sd.Add("subject","Test Email")
$w = Get-SPWeb http://somedomain.com
$body = "This is a test email sent from SharePoint, Wowee!!"

try {
	[Microsoft.SharePoint.Utilities.SPUtility]::SendEmail($w,$sd,$body)
}
finally {
	$w.Dispose()
}

This is a pretty straightforward script. It builds a StringDictionary with some obvious entries and then calls the SendEmail function of the SPUtility class to have SharePoint send the email. Just replace the dictionary entries with appropriate values and be sure to switch the web address (line 5) to one of yours. To run it, open the SharePoint Management Shell (You’ll want to run-as a farm administrator) and navigate to the location of the script. You can run it by typing: .\TestEmail.ps1

TestEmail

If you switch back over to where you are running smtp4dev you should see a new entry:

smtp4devTestEmail

You can double click the message to open it in your mail program:

MessageTestEmail

You can also click Inspect to see all the details:

InspectTestEmail

 

That’s it! Just be sure to switch the settings back if this was a temporary test. Of course, as long as you use your real email address in the to entry above then you can run the TestEmail.ps1 script again to ensure things are sending properly after you switch things back.

Top Link Bar Navigation From XML

Applies To: SharePoint

In my last post, Top Link Bar Navigation To XML, I provided you with a script to serialize a site collection’s Global Navigation Nodes to XML. In the post before that, Multi-Level Top Link Bar Navigation (Sub Menus), I showed you how to enable additional sub menus using the native control in SharePoint by editing a simple attribute in the Master Page. However, it quickly became clear that the native navigation editor (Site Actions > Site Settings > Navigation) won’t allow you to edit anything greater than 2 levels despite the control’s support for it. In this final post I’ll show you how to edit the exported XML to add multiple levels and then to import it.

The Script

Copy and paste the code below into notepad and save it as NavigationFromXml.ps1

$xmlFile = "d:\scripts\navigation\ExportedNavigation.xml"
$sourcewebApp = "http://somesite.com"
$destweb = "http://somesite.com"
$keepAudiences = $true #set to false to have it totally ignore audiences (good for testing)

Function CreateNode($xNode,$destCollection){
    Write-Host $xNode.Title

    #make URLs relative to destination
    $hnUrl = $xNode.Url
    #Write-Host "O URL: $hnUrl"
    $hnUrl = SwapUrl $hnUrl $sourceWebApp $destWeb
    #Write-Host "N URL: $hnUrl"

    $hnType = $xNode.NodeType
    if($hnType -eq $null){
        $hnType = "None"
    }

    $hNode = [Microsoft.SharePoint.Publishing.Navigation.SPNavigationSiteMapNode]::CreateSPNavigationNode($xNode.Title,$hnUrl,[Microsoft.SharePoint.Publishing.NodeTypes]$hnType, $destCollection)
    $hNode.Properties.Description = $xNode.Description
    if($keepAudiences){
        $hNode.Properties.Audience = $xNode.Audience
    }
    $hNode.Properties.Target = $xNode.Target
    $hNode.Update()

    if($xNode.Children.Count -gt 0) {
        foreach($child in $xNode.Children) {
            CreateNode $child $hNode.Children
        }
    } elseif($xNode.Children.IsNode -eq "yes") {
        #single child
        CreateNode $xNode.Children $hNode.Children
    }
}

Function SwapUrl([string]$currentUrl,[string]$sourceRoot,[string]$destRoot) {
	if ($currentUrl -ne "/") {
		if ($currentUrl.StartsWith("/")) {
            #Relative URL
			$currentUrl = $sourceRoot + $currentUrl
		} elseif ($currentUrl.StartsWith($destRoot)) {
            #May be a problem for non root sites
			$currentUrl = $currentUrl.Replace($destRoot,"")
		}
	} else {
		$currentUrl = [System.String]::Empty
	}
	$currentUrl
}

$dw = Get-SPWeb $destweb
$pdw = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($dw)

$gnn = Import-Clixml $xmlFile

$nodeCount = $pdw.Navigation.GlobalNavigationNodes.Count
Write-Host "  Removing Current Nodes ($nodeCount)..." -ForegroundColor DarkYellow
for ($i=$nodeCount-1; $i -ge 0; $i--) {
    $pdw.Navigation.GlobalNavigationNodes[$i].Delete()
}

if($gnn.IsNode -Eq "yes"){
    #not an array (just 1 top level nav item)
    #Write-Host "1 Only!"
    [void](CreateNode $gnn $pdw.Navigation.GlobalNavigationNodes)
} else {
    #array of nodes, so add each one
    foreach($n in $gnn){
        [void](CreateNode $n $pdw.Navigation.GlobalNavigationNodes)
    }
}

#cleanup
$dw.dispose()

What it Does and How to Use It

There are 4 parameters at the top of the script for you to adjust:

  • $xmlFile: The path to use for the XML input
  • $sourcewebApp: The URL of the web application (Needed to ensure relative links are imported correctly)
  • $destweb: The URL of the site collection you are importing the navigation nodes to
  • $keepAudiences: When $true audience values are used, when $false they are ignored (helpful for testing)

Once you set those and save, open the SharePoint Management Shell (You’ll want to run-as a farm administrator) and navigate to the location of the script. You can run it by typing: .\NavigationToXml.ps1

RunNavigationFromXMLScript

The script will delete all global navigation nodes from the $destweb. It will then use the Import-Clixml command to hydrate a series of custom objects that it will use to build the new navigation nodes. It will build the nodes recursively allowing any number of levels of child nodes (You will have to adjust your Master Page as outlined in my post, Multi-Level Top Link Bar Navigation (Sub Menus), to see any more than the default 2 levels).

How it Works

The main code begins at line 53 where we retrieve the $destweb and then hydrate the $gnn object from the $xmlFile. One of the custom properties used in our NavigationToXml.ps1 script we output was IsNode. In PowerShell an array of one object does not serialize to an array. Rather, it serializes directly to the single object. Using IsNode allows us to know if the object we are working with is an actual node or an array of nodes so that we can avoid exceptions when accessing other properties.

For every node we hydrated we call the function CreateNode (lines 6-36) which creates a node using the custom properties in the passed collection. URLs are made relative to the web application using the function SwapUrl (lines 38-51). This will process every node in the collection along with all of their children.

Editing the XML

So why go through this at all? If you just want to copy global navigation from one site collection to another then just use the simpler NavigationPropagation.ps1 script provided in this article: SharePoint Top Link Bar Synchronization.

However, doing it this way allows you a chance to tweak the XML using notepad (I recommend notepad++). This is the easiest way to add Multiple Levels. For now I’ll explain the basics of the XML document and how to edit it. We’ll go over more of the whys in the Putting it All Together section below.

Structure

To get your base structure it’s best to use the native navigation editor (Site Actions > Site SettingsNavigation) and setup as many of your nodes as you can. Then you can use the NavigationToXml.ps1 script provided in the previous article, Top Link Bar Navigation To XML, as your base document. Trust me, trying to write it all from scratch is dumb. Here’s a quick summary of the results of running that script against navigation that looks like this:

IMG_0347

Skipping the automatic link for the site (Navigation Propagation) here is the current structure of those nodes:

  • Some Sites (Heading)
    • theChrisKent (AuthoredLinkPlain)
    • Microsoft (AuthoredLinkPlain)
  • Google (AuthoredLinkPlain)

Here’s what the exported XML looks like for that same set of nodes:

<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Object</T>
    </TN>
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Some Sites</S>
      <S N="Url">/personal/ckent/navtest</S>
      <S N="NodeType">Heading</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Obj N="Children" RefId="1">
        <TN RefId="1">
          <T>System.Object[]</T>
          <T>System.Array</T>
          <T>System.Object</T>
        </TN>
        <LST>
          <Obj RefId="2">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">theChrisKent</S>
              <S N="Url">http://thechriskent.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Obj N="Children" RefId="3">
                <TNRef RefId="1" />
                <LST />
              </Obj>
            </MS>
          </Obj>
          <Obj RefId="4">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">Microsoft</S>
              <S N="Url">http://microsoft.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Ref N="Children" RefId="3" />
            </MS>
          </Obj>
        </LST>
      </Obj>
    </MS>
  </Obj>
  <Obj RefId="5">
    <TNRef RefId="0" />
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Google</S>
      <S N="Url">http://google.com</S>
      <S N="NodeType">AuthoredLinkPlain</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Ref N="Children" RefId="3" />
    </MS>
  </Obj>
</Objs>

Each node can be found in an Obj element where you can easily find the list of custom properties in the MS section (IsNode, Title, Url, NodeType, Description, Audience, Target and Children). Each of these are simple strings. The only one that can be a little tricky is Audience which we’ll cover in depth in the Adding Nodes section below.

Each Obj element has a unique (to this document) value for it’s RefId. This is just an integer. The only thing to really note here is that each one needs to be unique. They will be the case in any export since this is part of the Export-Clixml command, but you’ll need to pay close attention to this when adding additional nodes. These are also used in Ref elements which you’ll see in various spots. If an object is the exact same as an object previously defined in the document it won’t be defined. Rather it will just get a Ref element instead of an Obj element. The Ref element will have a RefId that is equal to the previously defined Obj‘s RefId.

This mostly comes up with blank children. A good example is the first node with no children above is the node for theChrisKent (lines 22-38). You can see that the Children Obj is defined in lines 33-36. Whereas, the very next node without children (Microsoft lines 39-52) doesn’t have an Obj for Children but rather a Ref (line 50) with a RefId of 3 which you’ll recognize as the RefId specified in line 33. This can seem very confusing at first but it will get easier using the examples below.

Adding Nodes

Adding a single node with no children is pretty easy. Just cut and paste a similar node (Obj) and switch up the RefId to something unique for the document. For instance if I wanted to create another link under Some Sites right after the Microsoft one, I could just copy the Microsoft node (lines 39-52) and paste it directly below its closing tag (</Obj>). It would look something like this:

<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Object</T>
    </TN>
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Some Sites</S>
      <S N="Url">/personal/ckent/navtest</S>
      <S N="NodeType">Heading</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Obj N="Children" RefId="1">
        <TN RefId="1">
          <T>System.Object[]</T>
          <T>System.Array</T>
          <T>System.Object</T>
        </TN>
        <LST>
          <Obj RefId="2">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">theChrisKent</S>
              <S N="Url">http://thechriskent.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Obj N="Children" RefId="3">
                <TNRef RefId="1" />
                <LST />
              </Obj>
            </MS>
          </Obj>
          <Obj RefId="4">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">Microsoft</S>
              <S N="Url">http://microsoft.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Ref N="Children" RefId="3" />
            </MS>
          </Obj>
          <Obj RefId="10">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">Apple</S>
              <S N="Url">http://apple.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Ref N="Children" RefId="3" />
            </MS>
          </Obj>
        </LST>
      </Obj>
    </MS>
  </Obj>
  <Obj RefId="5">
    <TNRef RefId="0" />
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Google</S>
      <S N="Url">http://google.com</S>
      <S N="NodeType">AuthoredLinkPlain</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Ref N="Children" RefId="3" />
    </MS>
  </Obj>
</Objs>

Pretty straightforward overall. Notice that in line 53 I’ve set the RefId to 10. This is because the only requirement is for it to be unique – It does not have to be in sequence. If you run the NavigationFromXml.ps1 script on the above the site now looks like this:

TopLinkBarWithAppleLink

What about an additional level? For this example we’ll be adding a new sub menu under Some Sites called Pizza with 2 links. Our structure should look like this:

  • Some Sites (Heading)
    • theChrisKent (AuthoredLinkPlain)
    • Microsoft (AuthoredLinkPlain)
    • Apple (AuthoredLinkPlain)
    • Pizza (Heading)
      • Pizza Hut (AuthoredLinkPlain)
      • Little Caesars (AuthoredLinkPlain)
  • Google (AuthoredLinkPlain)

Here’s what our modified XML looks like now:

<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Object</T>
    </TN>
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Some Sites</S>
      <S N="Url">/personal/ckent/navtest</S>
      <S N="NodeType">Heading</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Obj N="Children" RefId="1">
        <TN RefId="1">
          <T>System.Object[]</T>
          <T>System.Array</T>
          <T>System.Object</T>
        </TN>
        <LST>
          <Obj RefId="2">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">theChrisKent</S>
              <S N="Url">http://thechriskent.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Obj N="Children" RefId="3">
                <TNRef RefId="1" />
                <LST />
              </Obj>
            </MS>
          </Obj>
          <Obj RefId="4">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">Microsoft</S>
              <S N="Url">http://microsoft.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Ref N="Children" RefId="3" />
            </MS>
          </Obj>
          <Obj RefId="10">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">Apple</S>
              <S N="Url">http://apple.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Ref N="Children" RefId="3" />
            </MS>
          </Obj>
          <Obj RefId="11">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">Pizza</S>
              <S N="Url">http://apple.com</S>
              <S N="NodeType">Heading</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Obj N="Children" RefId="12">
                <TNRef RefId="1" />
                <LST>
                  <Obj RefId="13">
                    <TNRef RefId="0" />
                    <ToString>System.Object</ToString>
                    <MS>
                      <S N="IsNode">yes</S>
                      <S N="Title">Pizza Hut</S>
                      <S N="Url">http://www.pizzahut.com</S>
                      <S N="NodeType">AuthoredLinkPlain</S>
                      <S N="Description"></S>
                      <S N="Audience"></S>
                      <Nil N="Target" />
                      <Ref N="Children" RefId="3" />
                    </MS>
                  </Obj>
                  <Obj RefId="14">
                    <TNRef RefId="0" />
                    <ToString>System.Object</ToString>
                    <MS>
                      <S N="IsNode">yes</S>
                      <S N="Title">Little Caesars</S>
                      <S N="Url">http://www.littlecaesars.com</S>
                      <S N="NodeType">AuthoredLinkPlain</S>
                      <S N="Description"></S>
                      <S N="Audience"></S>
                      <Nil N="Target" />
                      <Ref N="Children" RefId="3" />
                    </MS>
                  </Obj>
                </LST>
              </Obj>
            </MS>
          </Obj>
        </LST>
      </Obj>
    </MS>
  </Obj>
  <Obj RefId="5">
    <TNRef RefId="0" />
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Google</S>
      <S N="Url">http://google.com</S>
      <S N="NodeType">AuthoredLinkPlain</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Ref N="Children" RefId="3" />
    </MS>
  </Obj>
</Objs>

We’ve now added an object with NodeType set to Heading since this is required in order to support having children. We’ve also created a Children Obj that is not a Ref. It has a TNRef and a LST. Inside the LST we just add more of those AuthoredLinkPlain nodes like we did before. You can repeat this same trick for infinite levels down. Running NavigationFromXml.ps1 may result in something like this:

TopLinkBarWithPizzaNoSubMenu

What happened? We can see Pizza but it’s not a sub menu like we expected! These nodes are all there but by default SharePoint doesn’t show them. You’ll need to adjust your Master Page using the techniques in this article: Multi-Level Top Link Bar Navigation (Sub Menus). Once you’ve done that you’ll see something like this:

TopLinkBarWithPizzaAndSubMenu

What about Audiences? This one was a little trickier to get right. The easiest thing to do is apply an audience to a node using the built in navigation editor and then export it using NavigationToXml.ps1 to see what the value should be. But what about when you’ve already done that and you want to manually edit it? An actual audience (Not a SharePoint Group but a compiled audience) is just specified as the GUID followed by 4 semi-colons. If you wish to do more than one then just put a comma between the GUIDs and then add on 4 semi-colons on the end. Here’s what that looks like:

Single Audience:

<S N="Audience">45175fed-9cc8-45ad-9cd6-dda031bf7577;;;;</S>

Multiple Audiences:

<S N="Audience">0a6e46d0-8f45-487d-a249-141fe4a8c429,8b722c7b-a1b4-4f4f-8035-2bac6294b713;;;;</S>

Putting it All Together

This has been a 4 part series on how to improve Top Link Bar navigation:

I’ve provided you with 3 PowerShell scripts (NavigationPropagation.ps1, NavigationToXml.ps1 and NavigationFromXml.ps1) and told you how to make necessary changes to your Master Page. So how do we use all of these?

Because we want multiple sub menus we made the change to our Master Page(s) to set the MaximumDynamicDisplayLevels to 2 (2 is all we wanted, but feel free to go higher as needed). Then we setup our top level links and ran the NavigationToXml.ps1 script just to get our starter structure (We haven’t really needed it since). We made all the adjustments to add our sub menus and nodes then ran the NavigationFromXml.ps1 script to get all that populated.

For changes to our navigation we just update the XML file, run NavigationFromXml.ps1 and then run NavigationPropagation.ps1 to synchronize our changes across our site collections. It works really well. Hopefully you’ll find this system or some parts of it to be helpful too!

Top Link Bar Navigation To XML

Applies To: SharePoint

Continuing in my series on SharePoint’s Top Link Bar (sometimes called the Tab Bar) I want to show you how to serialize a site collection’s Global Navigation Nodes to XML using PowerShell. This isn’t quite the same as just using the Export-Clixml command since we are interested in very specific properties and their format.

In my previous post, Multi-Level Top Link Bar Navigation (Sub Menus), I showed you how to enable additional sub menus using the native control in SharePoint by editing a simple attribute in the Master Page. However, it quickly became clear that the native navigation editor (Site Actions > Site Settings > Navigation) won’t allow you to edit anything greater than 2 levels despite the control’s support for it. In this post I’ll show you how to output a site’s navigation to XML. In my next post I’ll show how to edit it and then import it to create multiple levels.

The Script

Copy and paste the code below into notepad and save it as NavigationToXML.ps1

$xmlFile = "d:\scripts\navigation\ExportedNavigation.xml"
$sourceweb = "http://somesite.com"
$keepAudiences = $true

Function ProcessNode($nodeCollection) {
    $parentCollection = @()
    foreach($node in $nodeCollection) {
       if ($node.Properties.NodeType -ne [Microsoft.SharePoint.Publishing.NodeTypes]::Area -and $node.Properties.NodeType -ne [Microsoft.SharePoint.Publishing.NodeTypes]::Page) {
	   $nHash = New-Object System.Object
           $nHash | Add-Member -type NoteProperty -name IsNode -value "yes"
           $nHash | Add-Member -type NoteProperty -name Title -value $node.Title
           $nHash | Add-Member -type NoteProperty -name Url -value $node.Url
           $nHash | Add-Member -type NoteProperty -name NodeType -value $node.Properties.NodeType
           $nHash | Add-Member -type NoteProperty -name Description -value $node.Properties.Description
           if($keepAudiences){
                $nHash | Add-Member -type NoteProperty -name Audience -value $node.Properties.Audience
           } else {
                $nHash | Add-Member -type NoteProperty -name Audience -value ""
           }
           $nHash | Add-Member -type NoteProperty -name Target -value $node.Target
           if($node.Children.Count -gt 0){
                $nHashChildren = ProcessNode($node.Children)
                $nHash | Add-Member -type NoteProperty -name Children -value $nHashChildren
           } else {
                $nHash | Add-Member -type NoteProperty -name Children -value @()
           }
           $parentCollection += $nHash
       }
    }
    $parentCollection
}

$sw = Get-SPWeb $sourceweb
$psw = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($sw)

ProcessNode($psw.Navigation.GlobalNavigationNodes) | Export-Clixml $xmlFile

#cleanup
$sw.dispose()

What it Does and How to Use It

There are 3 parameters at the top of the script you will need to change:

  • $xmlFile: The path to use for the XML output
  • $sourceweb: The URL of the site whose navigation you are serializing
  • $keepAudiences: When $true audience values are serialized, when $false they are left blank

Once you set those and save, open the SharePoint Management Shell (You’ll want to run-as a farm administrator) and navigate to the location of the script. You can run it by typing: .\NavigationToXml.ps1

RunNavigationToXMLScript

The script will create an array of custom objects that correspond to the $sourceweb‘s navigation nodes and then use standard PowerShell serialization to create a simplified XML document at the location specified by $xmlFile. For instance, given the navigation shown here:

IMG_0347

The XML output will look similar to this:

<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Object</T>
    </TN>
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Some Sites</S>
      <S N="Url">/personal/ckent/navtest</S>
      <S N="NodeType">Heading</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Obj N="Children" RefId="1">
        <TN RefId="1">
          <T>System.Object[]</T>
          <T>System.Array</T>
          <T>System.Object</T>
        </TN>
        <LST>
          <Obj RefId="2">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">theChrisKent</S>
              <S N="Url">http://thechriskent.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Obj N="Children" RefId="3">
                <TNRef RefId="1" />
                <LST />
              </Obj>
            </MS>
          </Obj>
          <Obj RefId="4">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">Microsoft</S>
              <S N="Url">http://microsoft.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Ref N="Children" RefId="3" />
            </MS>
          </Obj>
        </LST>
      </Obj>
    </MS>
  </Obj>
  <Obj RefId="5">
    <TNRef RefId="0" />
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Google</S>
      <S N="Url">http://google.com</S>
      <S N="NodeType">AuthoredLinkPlain</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Ref N="Children" RefId="3" />
    </MS>
  </Obj>
</Objs>

For those familiar with the Export-Clixml PowerShell command this shouldn’t look too crazy. For the rest of us, I will go into detail about this in my next post. Regardless of how complicated that may look to you, it is much simpler than if we had just called Export-Clixml on the Global Navigation Nodes object for the site.

How it Works

The main code begins at line 33 where the $sourceweb is retrieved and then the ProcessNode function (Lines 5-31) is called on the GlobalNavigationNodes collection. This generates an array of custom objects that are then serialized to the $xmlFile using Export-Clixml.

The ProcessNode function creates a custom object for every node and captures the Title, Url, NodeType, Description, Target, and when $keepAudiences is $true the audience information. Every custom object is also given a Children property (lines 21-26) and this is set to an array recursively for all child nodes that exist. This has been written to capture any number of levels of children. (This script does, however, skip all Area and Page node types since these are automatic and shouldn’t be edited manually)

 

So what do we do with this XML document? In my next post I’ll show you how to read, edit and ultimately import these nodes back into the site collection. This can be helpful to copy nodes from one site colleciton to another (although my post on SharePoint Top Link Bar Synchronization provided a much cleaner approach for this). More importantly this will provide you an easy way to have multiple sub menus in the SharePoint Top Link Bar.