Remapping Keystrokes in MorphX

The shortcut keys used in MorphX, the Dynamics AX code editor, are almost exactly the same as those used in Visual Studio. In fact, the code editor basically is the editor from Visual Studio, with somewhat reduced functionality, if I understand correctly.

The one thing that’s always bugged me about it is that the keystrokes for commenting and un-commenting code are different. In VS (and various other editors), it’s Ctrl-K,Ctrl-C and Ctrl-K,Ctrl-U. For no obvious reason, MorphX uses Ctrl-E,Ctrl-C and Ctrl-E,Ctrl-U. This isn’t too bad, until you start getting used to it, then you accidentally press Ctrl-E in SQL Management Studio, hence executing a block of SQL instead of commenting it out. After doing that a few times, I decided that I needed to fix MorphX.

Surprisingly, I couldn’t find any facility built into AX for changing keyboard shortcuts. So, I turned to AutoHotKey. It’s very easy to remap a single keystroke in AHK. For instance, I can just remap Ctrl-K to Ctrl-E with “^k::^e”. I went ahead and did that for awhile, since it didn’t really seem that there would be any harm in that. But, I wanted to figure out how to create a more targeted replacement, so only the two specific commands would get remapped.

The snippet below does that. And, or course, it limits the remapping to the AX code editor.

; https://gist.github.com/andyhuey/5466566
; comment/uncomment, the way it was intended to be...
#IfWinActive ahk_class AxMainFrame
^k::
Transform, CtrlC, Chr, 3
Transform, CtrlU, Chr, 21
Input Key, L1 M T1
if Key = %CtrlC%
     Send ^e^c
if Key = %CtrlU%
     Send ^e^u
return
#IfWinActive

Backup Script

In my last post, I mentioned that I was going to write up some of the utility scripts I have on my VM. The first one is pretty simple. It’s a little PowerShell script to zip up the My Documents folder on the VM, and copy it to the physical machine. (I’m using 7-Zip.)

There are a few things in this script that are pretty common tasks that I need to do when using PowerShell, so this is a good thing to put up on the blog for reference. Just to point out those things:

  1. Creating a file name that contains the current date.
  2. Running a command that’s in a string variable.
  3. Prompting to “press any key” when done, so the user can see error messages, if the script is being run from a desktop icon.
  4. Giving an option to skip the “press any key” prompt, when the command is run unattended from task scheduler.
# https://gist.github.com/andyhuey/5466524
param(
     [switch]$quiet
)
$zipExe = "C:\Program Files\7-Zip\7z.exe"
$dateStr = '{0:yyyy-MM-dd}' -f (Get-Date)
$buFileName = "\\my-machine\c$\Users\me\Documents\backup\VM_MyDocBU_" + $dateStr + ".7z"
$myDocs = "C:\Users\me\Documents"
pushd
cd $myDocs
& $zipExe a -r $buFileName $myDocs
popd
if (!$quiet)
{
     Write-Host "Press any key to continue ..."
     $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}

Hard drive crash

One of the hard drives on my work PC crashed a couple of days ago. My work PC is (or rather, was) configured with an SSD for a boot drive, and two regular SATA drives, in a RAID 0 configuration, for a secondary data volume. It was one of those SATA drives that failed. Since RAID 0 doesn’t have any redundancy built in, that killed the volume.

The only data I had on that volume were the files for my VM. The way we have developer machines configured here, we have general productivity stuff (Office, etc) on the boot volume, and all the developer stuff on the VM. The setup for developing for Dynamics AX is fairly complicated, so it makes sense to do it on a VM.
Unfortunately, we don’t have any facility set up for backing up our VMs anywhere. Also, between the way AX stores source files, and the way we have TFS set up, we don’t always check in code daily, nor do we have a simple way of backing up in-progress code changes that haven’t been checked in. So, the end result is that I lost about two days worth of work on my current project.
I had, at one point, written a backup script (using PowerShell and 7-Zip) to back up the My Docs folder on the VM to the My Docs folder on the physical machine, but I hadn’t ever set it to run on a scheduled basis, so the backup file there was about a week old, which meant that I also lost a few SQL files, some test spreadsheets, and one quickie VS 2010 project that I’d written to test a web service. Oh, and I was keeping the backup script itself (plus some other scripts) in a ‘util’ folder on the root of the VM C: drive, so those didn’t get backed up either, and were lost.
So the takeaway from all of this, of course, is that I need to do what I can to get around the limitations of the environment I’m working in, and set up some automated backup procedures.
In terms of backing up the My Docs folder, I rewrote my lost PowerShell script, and set it up in task scheduler to run at 6pm daily. It ran fine last night, and I think it’ll work fine on a continuing basis.
In terms of backing up in-progress work in AX, I extended the ‘startup projects’ class that I blogged about recently to also allow me to export all of my active projects. I have it exporting them to a folder under the My Docs folder, so, if I run the export at the end of the day, prior to the file system backup, I should always have a backup of my current work, in a format that I can re-import into AX, if need be.
There are still some big holes in this system, including the fact that I have to remember to run that export daily. But it’s a good start. I’d like to add some extra stuff to this process, including daily SQL backups, and maybe a push of certain backup files to the cloud. The SQL backups are kind of hard, since the AX test database is 70 GB. And my employer, for some reason, likes to block access to cloud-based backup & storage providers, so I can’t just copy stuff into a DropBox folder, so that part’s a little tricky too. 
I’ve also considered setting up a local Mercurial or Git repo, checking in the AX export files every day, and pushing them up to a private Bitbucket repo. This would give me offsite backup, with the added benefit of increased granularity and visibility, but it would probably violate one or more corporate policies.
As a follow-up to this post, I’m going to write a few more posts, about some of the scripts I’m using now.

Opening multiple projects in MorphX at startup

The MorphX IDE used for X++ programming in Microsoft Dynamics AX is a fairly decent environment to work in, but it definitely has some shortcomings and oddities. There is a “project” abstraction in MorphX that allows you to create a named group of objects that all relate to a project that you are working on. There’s little meaning to these projects, other than that. You can export all objects in a project into a single XPO file, but other than that, it’s basically just a structure to help a programmer keep track of a list of related objects. You can set a single project as your “startup project”, and that project will then open automatically when you open MorphX. Since I’m usually juggling three or four projects at a time, I’ve been thinking that it would be great if you could open a group of projects instead of just one, so I decided to try and write some code to do that.

My realization that this could be done at all mostly came from the book Microsoft Dynamics AX 2012 Development Cookbook. The section “Modifying the right-click context menu” described a method of setting and clearing the startup project via the context menu. Prior to reading this, I hadn’t realized that the development environment was as customizable as the AX front-end, though (in retrospect) it makes sense that it is.

The project I’m going to describe here will do a few, fairly simple, things:

  1. Maintain a list of startup projects in a simple text file, stored in my personal “documents” folder.
  2. Allow for adding and removing projects from this list, via the right-click context menu.
  3. Allow the user to open all projects in this list, via the MorphX “tools” menu.

I could probably do a lot more with this project, such as actually open the projects at MorphX startup, rather than just through the tools menu, but I’m fine with the project as-is, for now.

This blog post is going to walk through the steps necessary to implement this functionality.

First, we’re going to create a class called “AjhDevLaunchStartupProjects”. Our class will have methods to add and remove projects from the list, and to open all projects specified in the list. We’re not going to go overboard with efficiency here. I’m assuming that the list will, at most, have three or four projects on it at a time, so we’re simply going to read them from disk into an array, and write them back out from the array.

We’ll start with an entirely standard static constructor:

// https://gist.github.com/andyhuey/5315492
public static AjhDevLaunchStartupProjects construct()
{
    return new AjhDevLaunchStartupProjects();
}

Now let’s add a method to get the file name for the text file:

// https://gist.github.com/andyhuey/5315509
private str getStartProjFileName()
{
    str myDocsPath;

    #WinAPI

    myDocsPath = WinAPI::getFolderPath(#CSIDL_PERSONAL);
    return myDocsPath + @"\axStartProjects.txt";
}

Now, methods to read and write the file:

// https://gist.github.com/andyhuey/5315524
private Array getProjectList()
{
    // get the project list from a file.
    str startProjFileName;
    TextBuffer tbProjList;
    Array projects = new Array(Types::String);
    int nProjects;

    startProjFileName = this.getStartProjFileName();
    tbProjList = new TextBuffer();

    // if it doesn't exist, create an empty file & return.
    if (!WinAPI::fileExists(startProjFileName))
    {
        WinAPI::createFile(startProjFileName);
        return projects;
    }

    // should probably assert permission...
    tbProjList.fromFile(startProjFileName);

    nProjects = 0;
    while (tbProjList.nextToken(true))
    {
        nProjects++;
        projects.value(nProjects, tbProjList.token());
    }
    return projects;
}

private boolean writeProjectList(Array projects)
{
    // write out a project list to file, overwriting existing list.
    str startProjFileName;
    TextBuffer tbProjList;
    int i;

    startProjFileName = this.getStartProjFileName();

    tbProjList = new TextBuffer();

    for (i=1; i <= projects.lastIndex(); i++)
    {
        tbProjList.appendText(projects.value(i));
        tbProjList.appendText("\n");
    }

    return tbProjList.toFile(startProjFileName);
}

And the methods to add and remove projects from the list:

// https://gist.github.com/andyhuey/5315547
private void addProject(str newProject)
{
    // add a new project to the list.
    Array projects;
    int i;

    projects = this.getProjectList();

    // make sure it's not already there...
    for (i=1; i <= projects.lastIndex(); i++)
    {
        if (projects.value(i) == newProject)
            return;
    }
    // add it and save.
    projects.value(projects.lastIndex()+1, newProject);
    this.writeProjectList(projects);
}

private void removeProject(str projectToRemove)
{
    // remove a project from the list.
    Array projectsIn, projectsOut;
    int i, j;

    projectsIn = this.getProjectList();
    projectsOut = new Array(Types::String);

    j=1;
    for (i=1; i <= projectsIn.lastIndex(); i++)
    {
        if (projectsIn.value(i) != projectToRemove)
        {
            projectsOut.value(j, projectsIn.value(i));
            j++;
        }
    }
    this.writeProjectList(projectsOut);
}

And here’s the code to open all active projects:

// https://gist.github.com/andyhuey/5315552
public void openAllProjects()
{
    // get the project list, and open all projects.
    Array projects;
    int i;
    ProjectNode sharedProjects, privateProjects, projectNode;

    projects = this.getProjectList();

    sharedProjects = Infolog.projectRootNode().AOTfindChild('Shared');
    if (!sharedProjects)
        throw error("Error: cannot locate shared project node!");

    privateProjects = Infolog.projectRootNode().AOTfindChild('Private');
    if (!privateProjects)
        throw error("Error: cannot locate private project node!");

    for (i=1; i <= projects.lastIndex(); i++)
    {
        projectNode = sharedProjects.AOTfindChild(projects.value(i));
        if (!ProjectNode)
            projectNode = privateProjects.AOTfindChild(projects.value(i));

        if (projectNode)
            projectNode.getRunNode();
        else
            warning(strFmt("Project %1 cannot be found.", projects.value(i)));
    }
}

(I probably found the code to open a project from this blog post, or a similar one.)
Now, we will create three new “action” menu items:

  1. AjhDevStartupProjectAdd – to add a project.
  2. AjhDevStartupProjectRemove – to remove a project.
  3. AjhDevStartupProjectOpenAll – to open all projects.

Each one will have ObjectType set to “Class”, and the object will be our class, “AjhDevLaunchStartupProjects”. The static main() method in our class will use args.menuItemName() to determine which action to take.
Here’s that method:

// https://gist.github.com/andyhuey/5315559
public static void main(Args args)
{
    AjhDevLaunchStartupProjects obj = AjhDevLaunchStartupProjects::construct();
    SysContextMenu contextMenu;
    str projectName, x;
    ;

    // should always be called from a menu item.
    if (!args.menuItemName())
    {
        return;
    }

    // if called from the add-ins context menu...
    if (SysContextMenu::startedFrom(args))
    {
        contextMenu = args.parmObject();
        projectName = contextMenu.getFirstNode().treeNodeName();
    }

    switch (args.menuItemName())
    {
        case menuitemActionStr(AjhDevStartupProjectAdd):
            obj.addProject(projectName);
            break;
        case menuitemActionStr(AjhDevStartupProjectRemove):
            obj.removeProject(projectName);
            break;
        case menuitemActionStr(AjhDevStartupProjectOpenAll):
            obj.openAllProjects();
        default:
            return;
    }
}

The add and remove menu items will be added to the SysContextMenu. This way, they will show in the context menu under “add-ins”. The “open all” menu item will be added to the DevelopmentTools menu. This way, it will show under the “Tools” menu in MorphX.

We will also change the “verifyItem” method of the “SysContextMenu” class, so that the add & remove items will only show in the context menu for projects (and not for other objects). (If we wanted to go further with this, we would also add logic here to show only one or the other option, depending on whether or not the project is already on the startup list.)

Here’s the code that we will add to “verifyItem”, at the end of the large case statement there:

    // https://gist.github.com/andyhuey/5315566
    // ajh 2013-04-03: my own startup project thing...
    case menuitemActionStr(AjhDevStartupProjectAdd):
    case menuitemActionStr(AjhDevStartupProjectRemove):
        if (firstNode.handle() != classNum(ProjectNode) || !match(#pathProjects, firstNode.treeNodePath()))
        {
            return 0;
        }
        return 1;

So I think this is all pretty straightforward. My purpose in writing this up in such detail was largely so that I could get a bit of X++ code on this blog, and in the hope that someone else might find this useful. I haven’t had the chance to write much general-purpose code at my current job, so there isn’t much I’m working on that would be appropriate to post here, but this seemed like a worthwhile little project to work on, and hopefully it may prove helpful to someone else.

no more Backpack

I’ve been using 37signals’ Backpack product for several years now, since February 2007. It turns out that 37signals retired the product back in June 2012, and is no longer accepting new customers for it. It still works fine for me, and they haven’t said anything about shutting it down entirely. I don’t recall ever seeing a notice on my Backpack page letting me know that they were retiring the product, but maybe I missed it. And, when I go to my account maintenance page now, there’s no indication there either; it still shows the same plan upgrade options that it always has. I’m still on the $7/month plan, and happy with that. If they’re not actually going to shut down the service for existing users, I might as well stick with it for the time being.

I use Satchel on my iPhone and iPad to access by Backpack account. It also still works, but hasn’t been updated since 2010, and likely won’t be updated again. In fact, I don’t see the full version as available in the App Store anymore, so I guess I need to be careful not to lose the copy in my iTunes library.

Since 37signals is still charging for Backpack, and since it (likely) runs on the same platform as their other services, I imagine they’ll keep it running for the foreseeable future. There’s no reason for them to shut down a service that’s generating revenue, and probably not costing them much money to keep running.

Still, it seems prudent to look into alternatives. 37signals would probably like people to move from Backpack to Basecamp, but that wouldn’t make much sense for me. Basecamp starts at $30/month, and isn’t really meant to be used as a single-person personal organizer.

Likewise, Papyrs would probably like to grab up some of Backpack’s customer base. They actually have a blog post about Backpack’s retirement on their site that’s interesting and well-written. Papyrus isn’t a good fit for me either though. It starts at $49/month and is really meant to be used for small company intranets.

There’s a good list of Backpack alternatives at http://alternativeto.net/software/backpack/. The first item on their list is Evernote, which I do use. But I’m not sure I’d like it as a replacement for Backpack. Second on their list is OneNote, which I also use, but also isn’t quite what I would want as a Backpack replacement.

From a GTD usage standpoint, I’ve tried Nozbe, and I’m curious about Asana. Maybe a combination of Evernote and Nozbe would work out for me?

I may write up some more notes on this subject, as it’s the kind of thing I enjoy messing around with and writing about, but for now, I’ve got other stuff to do today…

My new job, Dynamics AX, and X++

I started a new job (at SHI) back in January. I’ve been wanting to post something about it for awhile now, but I’ve been pretty busy. Also, I kind of wanted to keep quiet about it for a bit, just in case it didn’t work out. Well, I’ve been there for about two months now, and it seems to be going well.

Right now, I’m doing development for our Dynamics AX system, using AX’s proprietary programming language, X++. It’s a reasonably decent and relatively modern language, very similar to C# and/or Java. I do miss the more chaotic environment I’d previously been working in, where I was using a mix of ASP.NET / C#, JavaScript, and PHP / Drupal, depending on the project. SHI does have a fairly mixed environment, but there are enough programmers working here that they’re not likely to need me on anything other than X++ any time soon, so I guess I’ll have to get used to a bit less variety than I’ve had in the past.

The development environment built into AX is called MorphX. (This is also the name of a mediocre XBox 360 game, which kind of skews Google results for MorphX, but that’s OK.) Microsoft has obviously made some effort to add some nice features to MorphX since they acquired AX, but it’s not quite up to the standard set by Visual Studio. They’ve also tried to standardize some of the keyboard shortcuts between VS and MorphX, but there are still a few annoying inconsistencies there.

I recently found a project on CodePlex called Microsoft Dynamics AX 2012 X++ Editor Extensions, which adds a few missing features to the X++ code editor. I tend to worry about add-ins like this slowing things down or introducing instability, but these three extensions all seem to work well. (It’s funny how you don’t really think of, for instance, brace matching as being a big deal, until you don’t have it…)

I haven’t really blogged much about programming recently, so I’d really like to get back into the habit. I have a few possible topics in mind for AX-related posts, so hopefully I can find the time to write those up soon.

jailbreak

I jailbroke my iPhone on Friday, using evasi0n. (Apparently, I am one of many!) The process was fairly simple, and there haven’t been any negative side-effects, so far. I’ve been pretty happy with my iPhone, but there have been a few, fairly minor, things that I’ve wanted to do, but that weren’t possible with normal Apple-approved apps. Now that I’m jailbroken, I can fix a few of these little items.

First, I’ve always wanted to have more useful info show up on the lock screen. I played around with both IntelliScreen and LockInfo this weekend. I’m liking LockInfo a bit more. Intelliscreen can give you access to quite a lot of info on the lock screen, but I really don’t want that much, so LockInfo seemed a better fit for me. Mostly, I just wanted weather info. I always check the weather before I leave the apartment in the morning. Now, I can do that without unlocking the phone. A minor thing, but nice.

I’ve also got f.lux installed, which is kind of nifty. And SBSettings, which is pretty useful. And BiteSMS, which is a bit of an improvement on the standard iOS SMS app.

paperwork

I just spent the last couple of hours cleaning up old financial paperwork. So, if you’re not interested in that kind of thing, move along, nothing to see here, etc, etc.

I’ve blogged before about my continuing struggle to keep up with my Merrill Lynch accounts, in terms of entering the statements into Quicken. For quite a while, I’ve resisted letting Quicken download those accounts, since there tended to be some annoying little problems with that process. So, I kept manually entering them. Well, I fell off that bandwagon at the end of 2010, and had just been letting the statements pile up since. As David Allen would say, I’d gone numb to it.

I used to just have two accounts with Merrill — a catch-all account (called a CMA), and a Roth IRA. Well, now, I have the CMA, the Roth IRA, a traditional IRA (created when I rolled over the 401(k) from NMS), and an inherited IRA (from my Mom). So, that’s really too much to keep up with. Today, I decided to “clear the decks,” as it were, and get everything set up to download into Quicken. I started by shredding a bunch of old statements, from 2008-2010, that were in my filing cabinet. Then, I sorted out the big pile of 2011-2012 statements, and put them away, organized in a reasonable fashion, in the filing cabinet. Then, I set up all the accounts to download into Quicken. The result (in Quicken) is a little messy, but it all adds up correctly, and I guess that’s all I need.

My plan from this point forward is to open each statement as I get them, review it at a high level, then file it away. Meanwhile, the activity will get downloaded into Quicken any time I’m in there, balancing my checkbook, so that will be at least once a month.

I guess the main reason for this blog post is so that I can remember what I did, and when I did it, so that I can review things later in the year, and see if this system is working out. My main goal for this year is to actually look at the statements when I get them, so I can call Merrill if something is wrong, or if I need to change anything. Now that I won’t feel burdened by the need to do data entry every time I get a statement, hopefully I can avoid the temptation to just pile them up without looking at them!