New Year’s Day 2025

Well, I made it through another year, I guess, so here’s my usual New Year’s Day blog post! I went to bed around 9:30 PM last night, after watching the first two Thin Man movies on TCM. I got out of bed around 6:15 AM this morning. My days of staying up late on New Year’s Eve are pretty much done, apparently.

WordPress stats

I like to use these posts for both useful self-reflection and pointless (but fun) stats. I’m going to start with some pointless stats related to this blog. I ran a quick SQL statement to get my posts per year, over the life of this blog, and got the following:

blog posts per year chart
blog posts per year

So that’s 49 posts in 2024. My most active year was 2005, with over 200 posts. The least active full year was 2013, with 33 posts. I don’t know if any of that is super-interesting to anyone but me, but there it is.

I was trying to think of why 2013 would be a low point, and I guess it probably had something to do with starting a new job that year. And that’s still my current job (SHI), almost 12 years later. (More about that later.)

I also looked at traffic stats on the blog. Nothing interesting to report there, except that December 2024 was my most active month ever, by a long shot, with more than 3000 views. All of that traffic was on one day though: December 9. And I’m pretty sure it was all search bots or AI training bots or whatever.

Health

I’ve been fighting a low-level cold since Thanksgiving (or thereabouts), so my health situation is pretty much business as usual. I remember having a pretty good run of “not being sick” at some point this year though. Maybe in the spring? I thought I’d made a note of it in Day One or somewhere, but I can’t quite pinpoint when it was or how long it was.

As for my weight: I started 2024 at around 160, and ended it at 165. I’ve been fairly stable at 165, plus or minus two, for the last three months. So I’m hoping that I’ve stopped gaining weight and have hit a stable point. (In 2023, I went from 150 to 160, so my gain this year was half of last year’s gain.) And I’m still logging all of my meals/snacks with LoseIt, as I have been doing since 2013.

I think I still need to do some work on getting my snacking under control. I need to cut down on cookies and pastries form the various bakeries and coffee shops here in town. (Having a good French bakery almost directly below my apartment, in the same building, has turned out to be a bit of a problem…)

On the exercise front, I’m doing good. I was going to look for some summary stats to support that, but I’ve just realized that Apple’s Fitness app doesn’t have any kind of “year in review” thing, similar to Apple Music Replay or any of the other end-of-year things that have gotten big over the last few years. Odd, since that would likely be really popular. You can get some yearly graphs in the Health app though. So, from that, I see that I’ve averaged about 45 minutes per day exercise and 550 calories per day on the “move” ring. That’s pretty good, and I see that it’s been pretty consistent over the whole year.

And, having written all this, I realized that I hadn’t gone for a walk yet, so I did that just now. Here’s a photo!

New Year's Day morning walk
New Year’s Day morning walk
Work

Wow, I could write a lot about work this year. My old boss retired at the end of 2023, so I’ve just finished out my first year under the new boss. I had a pretty solid relationship with the old boss, but I’m still working on building one with the new guy.

I had gotten used to near-perfect performance reviews from the old boss, so I was a bit surprised to get an average review from the new one. (Basically, a rating of 3 out of 5 on everything, and a lower raise than I usually get.) I have a feeling that he didn’t put that much thought into the review, since he doesn’t actually know that much about me or what I do. So I’m not too worried about him just clicking “3” down the row of questions on the review form; it’s not that I did anything wrong, he just doesn’t have much to go on. But I think I do need to try to build up some kind of relationship with him in 2025, if I can. It’s hard, since he’s in Texas and I’m in NJ, and since he has a fairly large number of direct reports, and responsibility for three main groups (AX, CRM, and ServiceNow). So I guess I need to just keep trying to be a good employee and make sure to do the stuff he wants me to do.

In terms of systems and projects this year: We’re still on AX 2012, and haven’t made any real progress towards moving to D365 F&O. Maybe that’s too simplistic a view. Some stuff is going on behind the scenes, I guess, but there’s really no concrete progress on the real work of moving off AX 2012 and getting to F&O. For 2025, we’re planning on upgrading our SQL environment and getting on the latest CU for AX, so that’s something, and likely a necessary start. At the start of 2024, we were saying that getting to F&O was a three-year project. I think it’s still at least a three-year project, and I’m not sure if 2025 is going to count as year one, or if 2025 will be more like “year zero” with the real project starting in 2026.

We’ve been going through what they call an “agile transition” over the last year.  We’ve been using something like scrum since 2022, but the new boss (and new IT management in general) has been trying to really formalize that. We now have a scrum master, daily stand-ups, and multiple standard meetings (the usual stuff). And our group has been broken up into two separate “feature teams.” Also, we’ve stopped doing weekly deployments, and now only do one deployment per sprint (every two weeks). So that’s been a lot to get used to. And for 2025, we’re switching from using Azure DevOps to Jira for our agile/scrum management. So, just when things are getting smooth, we’re going to upend it all again.

Learning and other fun Stuff

OK, that’s enough of the serious stuff. Now let’s go through some more fun stuff. Let’s start with my Goodreads year in books. Just 27 books for 2024, though some of the Wheel of Time books were pretty long (Lord of Chaos was 1049 pages.) I had set a goal for myself of finishing the WOT series in 2024, but I’m not quite done with the final book. So maybe I’ll finish it by the end of January 2025. I’m not quite sure what I’ll tackle next; WOT has been eating up a lot of my reading time. I have a bunch of Dresden Files and Laundry Files books in my TBR pile, so maybe some of those. Or Discworld? And I have so many comics piled up too!

In terms of professional learning, I only see three books on my list that count towards that, and they’re all fairly general books. I don’t seem to have learned any new programming languages in 2024, or anything else big.

Looking at my Pluralsight history, I see that I completed around 15 courses there in 2024. Some of those were work-specific, as part of Pluralsight “challenges.” Some were just stuff I wanted to learn on my own. So there’s a mix of agile/scrum stuff, C# stuff, AI stuff, and miscellaneous “soft skill” stuff.

At some point during the year, I started looking at maybe getting an AZ-204 certification. But I didn’t get very far with that, and I’ve pretty much dropped the idea now. Back in 2013, I also started thinking about a D365 F&O certification of some kind, but I’m not going forward with that either, at least not yet.

During my performance review, my boss said that he wants me to pursue a SAFe certification for 2025, so I’m starting to work on that. Honestly, I’m not too enthusiastic about it, but it’ll probably help my career, and I’m open-minded enough to give it a try, I guess. I’ve started a leaning path in Pluralsight, and a video series on O’Reilly. I might also try to read the SAFe Distilled book at some point too. I don’t know. This plan may fizzle out, honestly, but I’m going to give it a try.

Okay, back to fun stuff. Here’s my 2024 year in film, from Letterboxd. I watched a little over 100 movies this year. I started and ended the year with After the Thin Man. My five-star ratings for the year went to The Thin Man, Casablanca, When Marnie Was There, and 10 Years with Hayao Miyazaki. The only film on that list that was new to me was When Marnie Was There.

My Apple Music Replay is kind of weird. My top song for the year is Hell of a Ride, by Nourished by Time. My top album is Songs of Surrender, by U2. And my top artist is Bombay Bicycle Club. I guess those all kinda make sense, though they’re all a little surprising. I guess the U2 album being on the top is mostly because it’s a four-disc set, and I added it in January. Looking at albums that I added to my library this year, none of them really stand out. There are some really good ones, but nothing that really stuck enough for me to listen to a lot, or that really blew my mind. At the moment, I’m pretty enthusiastic about the new Joan Armatrading album.

Ten Years Ago

I’ve been doing this so long that I can now look at my post from ten years ago (and even twenty years ago) and try to think about some big picture stuff. (The post from 20 years ago is just a one-liner about a song, so that one’s not too valuable.)

So, ten years ago:

  • I noted that I’d gone from 200 pounds to 165 over the course of 2014. So I’m starting 2025 at the same weight at which I started 2015. I’m not sure how I feel about that, but it is what it is!
  • 2014 was my first full year at SHI. I’ve been there since, so overall that’s gone well, I think.
  • I talked a little about consulting and volunteering in that post. I haven’t done any consulting in a long time, and I’m not planning to. I think those days are done, unless I decide to do that part-time after I’m retired. And I haven’t done any volunteering either. I’d like to do some of that, but I’m getting to the point where I’m too tired to do much of anything outside of my normal salaried work. (And I’m OK with that.)
  • I finished 30 books in 2014, so that’s pretty similar to this year’s total.
  • I moved this blog to WordPress in 2014, so I guess I should have celebrated my ten-year WordPress anniversary in 2024. I’m still OK with WordPress (even with Matt Mullenweg’s shenanigans).

Calling a SOAP WCF web service from .NET Core

I had a problem at work today that I’d previously solved, almost exactly a year ago. The project I was working on then got almost completely rewritten, so the current version of that code doesn’t have any reference to calling WCF web services at all. I kind of remembered that I’d written up a blog post about it, but couldn’t find it, since I was searching for SOAP instead of WCF. So I’m writing a new blog entry, with “SOAP” in the title, so if I have the same problem again, and I search for “SOAP” again, I’ll at least find this post, with a reference to the previous post. (Having a blog comes in handy, when your present-day self has to solve a problem that your past self has solved, but forgotten about…)

I don’t really have anything to add to that previous post. One thing I will do, though, is post the actual code here, rather than just embed a gist, since I now have a syntax highlighting solution that won’t garble it the way the previous setup did.

// https://gist.github.com/andyhuey/d67f78f6568548f66aabd20eadff8acf
// old way:
        public async Task RunAsync()
        {
            CallContext context = new CallContext();
            context.Company = "axcompany";
            string pingResp = string.Empty;
            var client = new XYZPurchInfoServiceClient();
            var rv = await client.wsPingAsync(context);
            pingResp = rv.response;
            Console.WriteLine("Ping response: {0}", pingResp);
        }
/* app.config:
    <system.serviceModel>
        <bindings>
            <netTcpBinding>
                <binding name="NetTcpBinding_XYZPurchInfoService" />
            </netTcpBinding>
        </bindings>
        <client>
            <endpoint address="net.tcp://myserver:8201/DynamicsAx/Services/XYZPurchInfoServices"
                binding="netTcpBinding" bindingConfiguration="NetTcpBinding_XYZPurchInfoService"
                contract="XYZPurchInfoSvcRef.XYZPurchInfoService" name="NetTcpBinding_XYZPurchInfoService">
                <identity>
                    <userPrincipalName value="myservice@corp.local" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
*/

// new way:
	CallContext context = new CallContext();
	context.Company = "axcompany";
	string pingResp = string.Empty;
	var client = new XYZPurchInfoServiceClient(GetBinding(), GetEndpointAddr());
	var rv = await client.wsPingAsync(context);
	pingResp = rv.response;
	Console.WriteLine("Ping response: {0}", pingResp);
	
	private NetTcpBinding GetBinding()
	{
		var netTcpBinding = new NetTcpBinding();
		netTcpBinding.Name = "NetTcpBinding_XYZPurchInfoService";
		netTcpBinding.MaxBufferSize = int.MaxValue;
		netTcpBinding.MaxReceivedMessageSize = int.MaxValue;
		return netTcpBinding;
	}

	private EndpointAddress GetEndpointAddr()
	{
		string url = "net.tcp://myserver:8201/DynamicsAx/Services/XYZPurchInfoServices";
		string user = "myservice@corp.local";

		var uri = new Uri(url);
		var epid = new UpnEndpointIdentity(user);
		var addrHdrs = new AddressHeader[0];
		var endpointAddr = new EndpointAddress(uri, epid, addrHdrs);
		return endpointAddr;
	}

Calling a Dynamics AX WCF service from .NET Core

A big part of my job these days is interop between Dynamics AX and various external services/resources. A WCF service hosted in our AX environment is often a key part of that equation. With older .NET Framework applications, it’s easy to add a reference to a WCF web service. And I’ve done that so often that I could probably do it in my sleep. If I need to interface with a new AX service, I’ll generally just go through the “Add Service Reference” procedure, then copy & paste some code from a previous project and adjust it for my curent needs.

I was recently working on a new program that I decided to try to write using .NET Core instead of .NET Framework. It took me quite a while to figure out how to deal with calling an AX web service under .NET Core, so I thought I’d write it up, briefly, with a couple of sample code snippets.

First, there is a facility for adding a WCF service reference in a .NET Core 2 project in VS 2017. (I think this might have been missing in earlier versions of VS and/or earlier versions of .NET Core.) It’s pretty similar to the tool that works with .NET Framework projects, but there are a few key differences in the generated code. The biggest difference is that it doesn’t add anything to app.config/web.config, and in fact isn’t set up to read any configuration info from the config files at all. So you need to do the config in your code. (Of course, you can write your own code to read from your config file.) Anyway, it took a lot of trial and error before I figured out what I needed to do. There’s not as much documentation on this as there could be. So here’s a simple example, showing a bit of code (and config) from a .NET Framework project, and the equivalent code from a .NET Core project.

(I’m embedding it below as a Gist, since I can’t get WordPress to play nice with the XML config sample right now.)


// old way:
public async Task RunAsync()
{
CallContext context = new CallContext();
context.Company = "axcompany";
string pingResp = string.Empty;
var client = new XYZPurchInfoServiceClient();
var rv = await client.wsPingAsync(context);
pingResp = rv.response;
Console.WriteLine("Ping response: {0}", pingResp);
}
/* app.config:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_XYZPurchInfoService" />
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://myserver:8201/DynamicsAx/Services/XYZPurchInfoServices"
binding="netTcpBinding" bindingConfiguration="NetTcpBinding_XYZPurchInfoService"
contract="XYZPurchInfoSvcRef.XYZPurchInfoService" name="NetTcpBinding_XYZPurchInfoService">
<identity>
<userPrincipalName value="myservice@corp.local" />
</identity>
</endpoint>
</client>
</system.serviceModel>
*/
// new way:
CallContext context = new CallContext();
context.Company = "axcompany";
string pingResp = string.Empty;
var client = new XYZPurchInfoServiceClient(GetBinding(), GetEndpointAddr());
var rv = await client.wsPingAsync(context);
pingResp = rv.response;
Console.WriteLine("Ping response: {0}", pingResp);
private NetTcpBinding GetBinding()
{
var netTcpBinding = new NetTcpBinding();
netTcpBinding.Name = "NetTcpBinding_XYZPurchInfoService";
netTcpBinding.MaxBufferSize = int.MaxValue;
netTcpBinding.MaxReceivedMessageSize = int.MaxValue;
return netTcpBinding;
}
private EndpointAddress GetEndpointAddr()
{
string url = "net.tcp://myserver:8201/DynamicsAx/Services/XYZPurchInfoServices";
string user = "myservice@corp.local";
var uri = new Uri(url);
var epid = new UpnEndpointIdentity(user);
var addrHdrs = new AddressHeader[0];
var endpointAddr = new EndpointAddress(uri, epid, addrHdrs);
return endpointAddr;
}

view raw

wcf-example.cs

hosted with ❤ by GitHub

This example obviously isn’t applicable in all use cases. But I think it could point you in the right direction, if you’re trying to do this and you’re as befuddled as I was when I started this. I should also mention that reading the auto-generated code produced by the tool is somewhat useful, though the code is about as messy as most auto-generated code tends to be.

Some useful resources:

 

FizzBuzz

We’re hiring a new developer in my group at work, and my boss is including me in the interviewing process. It’s been a few years since I’ve done developer interviews, so I’m a bit rusty. I suggested having candidates do a FizzBuzz test on a whiteboard as part of the interview.

Jeff Atwood wrote a good post about FizzBuzz on his blog back in 2007. It seems like an overly simple test, but it can be quite useful. I’ve only been asked to do FizzBuzz once myself, and it was a good experience. The interviewer was really sharp and asked me a lot of good questions about how I could do it differently or why I chose to do something a certain way. He turned a simple 12-line program into a good conversation.

At very least, FizzBuzz should help filter out candidates who are exaggerating on their resumes. If you say you’ve got five years of C# experience and you can’t write a FizzBuzz program, you’re lying. The two candidates we’ve looked at so far both have an MS in Comp Sci, so they’re both better-educated than I am, at least, and they should both be able to handle FizzBuzz.

Anyway, it occurred to me that I never wrote a FizzBuzz program in X++. So here’s a short job to solve FizzBuzz in X++. I might post it to RosettaCode, if I get around to it. Not that the world really needs one more FizzBuzz solution.

static void AjhFizzBuzz(Args _args)
{
    /* Write a program that prints the numbers from 1 to 100. 
    If it’s a multiple of 3, it should print “Fizz”. 
    If it’s a multiple of 5, it should print “Buzz”. 
    If it’s a multiple of 3 and 5, it should print “Fizz Buzz”. 
    */
    int i;
    
    for (i = 1; i <= 100; i++)
    {
        if (i mod 3 == 0 && i mod 5 == 0)
            info("Fizz Buzz");
        else if (i mod 3 == 0)
            info("Fizz");
        else if (i mod 5 == 0)
            info("Buzz");
        else
            info(int2str(i));
    }
}

Five year work anniversary

I hit my five-year anniversary at SHI this week. I don’t really have much to say about that, but I thought I should mark it with a quick blog post. I mentioned SHI in my New Year’s Day post, so that covered my current status pretty well. I first mentioned the job on my blog in March 2013, after I’d been there for a couple of months. My current projects are a mix of straight Dynamics AX work in X++, some .NET stuff, using C#, and some research into Power BI. So it’s a pretty good mix. I think I might see some opportunities to do stuff with Power BI in the cloud and maybe some Azure stuff this year. So that could be fun.

We recently (finally) got current Visual Studio subscriptions at work, so I now have access to VS 2017 Pro and the other random fun stuff that comes with a VS subscription. (Previously, we had some kind of standalone licenses to VS 2012 and 2013.)

I’m always a little worried about stagnating and turning into “that guy” who has been doing COBOL programming for years and hasn’t learned anything new since the Nixon administration. But SHI seems to be giving me enough opportunities to work on new and interesting stuff, and I’m still trying to keep current independently, via services like Pluralsight and Safari, and podcasts like .NET Rocks and Hanselminutes. So I guess I’m still on the right career track.

Database snapshots

At work, we do our Dynamics AX development on VMs, set up with a full install of AX (SQL Server, AOS, and AX client). Prior to our R3 upgrade, we were using local VMs, under VMWare Workstation. This worked out quite well. One of my favorite things about this setup was the ease with which I could take VM snapshots, allowing me to run destructive tests, then roll back, fix bugs, and rerun the tests without having to jump through hoops to reset my environment or set up new test orders, or whatever. It was all pretty clean and easy.

But, after we upgraded to R3, we set up new VMs on vSphere. There are a number of advantages to this, but one disadvantage is that I don’t have rights on vSphere to snapshot my own VM. (I’m sure I could ask an admin to snapshot my VM, but the typical testing cycle of snapshotting, rolling back, fixing code, snapshotting again, etc., would probably annoy the admins.) So I’ve been looking for an alternative way to manage testing destructive processes.

I’ve settled on using SQL database snapshots. AX 2012 R3 stores all data in one database, and all code in a separate model database. (Versions prior to R2 mixed code and data in one database.) I’ve worked out a process by which I can pretty quickly take a snapshot, run my tests, delete the snapshot, and start again.

Given a database called DAX12_PROD, here’s a quick run-down on how to execute this process.

(1) Stop the AOS server.

(2) Create a snapshot:

CREATE DATABASE DAX12_PROD_SS1 ON
 ( NAME = DAX12_PROD, FILENAME = 'E:\your_sql_data_folder\DAX12_PROD_SS1.ss'
 ) AS SNAPSHOT OF DAX12_PROD

(3) Start the AOS & run your tests.

(4) Stop the AOS.

(5) Restore from the snapshot.

ALTER DATABASE DAX12_PROD
 SET SINGLE_USER WITH ROLLBACK IMMEDIATE

RESTORE DATABASE DAX12_PROD
 FROM DATABASE_SNAPSHOT = 'DAX12_PROD_SS1'

(6) If you’re done, then drop the snapshot.

DROP DATABASE DAX12_PROD_SS1

(6) Start the AOS.

So that doesn’t take too much effort and is pretty quick to run. The snapshot file is a sparse file, created with the same size as the actual database file, but not actually taking that much space on disk. So you don’t need to have a ridiculous amount of free space on your VM (as long as your test isn’t changing a ridiculous amount of data).

And yes, I know that it would be so much better if I could just run unit tests that don’t touch actual data, but it’s nearly impossible to do that for a lot of the stuff I have to do in AX. There are some interesting things you can do, in certain scenarios, like getting creative with setTmp, but that’s too simplistic for a lot of the testing I need to do.

Hosting a web browser on a Dynamics AX form

I’m working on an interesting little project at work right now. We use SharePoint to facilitate some workflow around our sales orders and purchase orders. But there’s currently no link between AX and SharePoint, so the sales and purchasing reps have to copy & paste information from AX to SharePoint forms. Not a huge deal, but a bit of a waste of time for everyone. So the idea was to add buttons to various forms in AX that would open a new SharePoint form, with certain fields pre-populated. I might write up some stuff on the SharePoint side of this later, but this post is going to be about the AX side.

The first (obvious) idea was just to launch an URL in the default web browser. And that works fine. Except that everyone is accessing AX through terminal servers. And, while IE is installed on those servers, the internet connection on those servers isn’t filtered the same way it is on end-user machines. So clever users could launch IE from AX, then navigate to restricted sites and possibly infect the terminal servers with malware. Which would be very bad.

My first thought was that there ought to be a way to launch IE on the end-user’s actual PC from the terminal server, but if there’s a way to do that, I can’t figure it out. (And it makes sense that there isn’t, really.) So my next thought was to launch the SharePoint site in a web browser control hosted in an AX form, with no address bar and no way to navigate away from that SharePoint site. Simple enough, right?

After a bit of web searching, I found this article on hosting an instance of System.Windows.Forms.WebBrowser in an AX form. I got pretty far with that, including preventing new windows from opening (which would allow them to break out of the control and into IE), and also preventing them from following links to other sites. But there was one key issue I couldn’t get past: the tab key and control keys wouldn’t work in the control. So the user wouldn’t be able to tab from field to field, or copy & paste information with Ctrl-C and Ctrl-V. I found a few references to this issue on StackOverflow and elsewhere, but no solutions that would have worked easily in Dynamics AX. (They mostly relied on doing things that would work in a real Window Forms app, in C++ or C#, but that I wasn’t going to be able to do in AX.)

So I punted on that, and decided to try just adding the ActiveX web browser control to the form. I’d never actually added an ActiveX control to a form; there’s a good overview about how to do that here. The most important thing I picked up from that is the “ActiveX Explorer” function that can be accessed form the context menu after you add an ActiveX control to a form. That’s how you hook into control events.

I managed to do everything I needed with the control:

  1. Set it to suppress JavaScript errors, via the silent flag. (Our SharePoint site has some messy JavaScript on it, that doesn’t cause any issues, but throws up some errors, if you don’t suppress them.)
  2. Prevent navigation outside the SharePoint site, which I can do by setting a cancel flag in the BeforeNavigate2 event handler.
  3. Prevent opening new windows, which I can do by setting a cancel flag in the NewWindow2 event handler.

And it handles the tab key and control keys normally, without any workarounds.

So that’s about it. ActiveX is a twenty-year-old technology, but it still works. As much as I would have liked to do something fancier, I can’t complain!

another busy week

My company’s upgrade to Dynamics AX 2012 R3 over the past weekend went well, all things considered. We’ve been having lingering problems all week, but we expected that we would, and we were prepared for it.

My hernia surgery is still scheduled for Monday, so I’ll be out all next week. I still have some open issues at work that I’ll have to close out or hand off to someone else today, but I’m sure they’ll get along fine without me.

Meanwhile, my warranty replacement SSD arrived from Samsung yesterday, so I can spend some time this weekend rebuilding my MacBook, hopefully. I have a tentative plan for doing a clean El Capitan install on the new drive, and then migrating my user data over from my old backup drive, but I’m not sure if it’ll work or not. Either way, I should have plenty of time to work on it next week.

A busy week

It’s been a busy week, starting with my follow-up visit to my doctor, where I found out I have a hernia. So a lot of my energy this week has been spent just thinking about that and planning for the surgery that I’m likely going to need.

And, at work, we’re in the last stretch of our upgrade from Dynamics AX 2012 RTM to R3. This is a really big update for us, as we have a lot of custom code, so there was a lot of work to do. The final upgrade is scheduled for this weekend, so I’ll have to work on Sunday. And the first week after the upgrade is liable to be a doozy, as various stuff we didn’t catch in testing shakes out.

Meanwhile, the RMA process for my MacBook’s SSD is plodding along. I got the RMA # and the UPS return label this week. I need to print out the label and package up the drive this weekend. I’ve been without my MacBook this week. and have been using my ThinkPad a lot, which is actually a pretty good experience, except that I keep wanting to use Emacs key bindings for a few things, like Ctrl-A and Ctrl-E for beginning of line and end of line. As soon as I get used to using ‘home’ and ‘end’, I’ll probably get the MacBook back up and running, and I’ll have to re-learn the Emacs key bindings.

And I’ve got my TiVo Bolt set up now. I dropped by the cable company office after work yesterday and picked up a CableCARD. I got it installed easy enough, but it took two calls to Optimum support to get it working. I think that “support tech roulette” gave me a clueless rep on the first go-round, and a more experienced one on the second. But both reps were polite and patient. Then it took me a couple of hours to set up the channels and my recordings. I’ve discovered that the channel line-up is a little different when you’re using a CableCARD than when you’re using a box. So I’m going to have to get used to some new channel numbers. The recordings seem to be working out OK too. I’ve got last night’s Daily Show, Nightly Show, and Late Show all on there, ready to watch. But I’ve also just figured out that I needed to re-run the guided setup to get the TiVo to fully recognize the channels that didn’t show up the first time around. Hopefully, that’s the last thing I’ll have to do, and it’ll be nice and stable now, with all the right channels and a fully-populated guide.

fun with credit card expiration dates

I’m doing some work on credit card processing right now. This is all related to my company’s upgrade from Dynamics AX 2012 RTM to 2012 R3. There were major changes to the credit card processing code in R2, and we’ve customized the code from RTM quite a bit, so there’s a lot to do. I’d like to write more about it, but it probably wouldn’t be of any general interest, plus I don’t want to get into any company-specific stuff.

But one very minor thing came up yesterday that was a little bit interesting. I noticed a support ticket from a user who was entering a new card, and wanted to enter an expiration date that wasn’t shown in the drop-down. (It was too far in the future.) This didn’t have anything to do with the upgrade, per se, but it made me curious enough to look at the code in RTM and in R3, to see what was being done. I was curious if maybe we had something in there like this example from The Daily WTF, or something where the upper-limit on the year was hard-coded to a specific year.

Well, it’s not quite that bad, but it is a bit questionable. In RTM, there’s a drop-down that is populated with years from (current year) to (current year + 7). In R3, it’s pretty similar, except that, when editing the expiration date after initial entry, you can go to current year + 8. (That’s probably an off-by-one error, rather than an intentional extension of the range.)

This all made me wonder what a reasonable upper-bound for expiration year would be, which lead me to this Stack Overflow question, which seems to indicate that there’s no agreed-upon maximum, but that it’s probably safe to go with 15-20 years. So Dynamics AX could stand to be be a bit more flexible on that range.

The other thing that bothers me about the way AX handles expiration dates is that (in 2012 RTM) they’re stored in the database as “MMYY”, which makes it difficult to sort and filter out expired cards. I was hoping that would be corrected in R3, but they’ve only changed it to “MM/YYYY” (without any leading zeroes on the month), which has the advantage of being more easily-readable by a human, and also of storing the full 4-digit year, but the disadvantage of being even more difficult to sort on, since the year sometimes starts in the 3rd position and sometimes in the 4th.

And let me end this blog post with a link to my very favorite publicly-documented method in AX, related to credit card processing: CreditCardWizard.codeCoverageFudge. I’m not entirely sure why this method exists in the released product, but I’m guessing it was added to fudge the code coverage numbers in some unit testing metrics. Maybe a certain percentage of code coverage had to be achieved, but there was no easy way to write meaningful tests for the remaining uncovered code, so they just wrote a method that does nothing, and a unit test for it?