Wednesday, February 15, 2017

C# - Testing that different cultures won't affect formatting

Ever worked on a system where you write code and test it and it all works perfect, but then maybe a unit test starts failing on a build server, or maybe a report looks wrong to the consumers of the report, all because it formatted a number to "12345,67" instead of "12345.67"?

This tip will help you.

First, lets assume we have this code:

public class ReportFormatter
{
  public string Format(decimal value)
  {
    return value.ToString();
  }
}

And a nice little unit test for it:

[TestClass]
public sealed class ReportFormatterTest
{
  [TestMethod]
  public void Format()
  {
    var sut = new ReportFormatter();
    
    var result = sut.Format(12345.67M);
    
    Assert.AreEqual("12345.67", result);
  }
}

This works perfectly fine. Lets even imagine that all our machines all have the same setup, and all are set to use the same regional settings. Great, nothing should ever break.

Until maybe Microsoft releases a patch to Windows that changes our regional settings to be "correct" - in fact, South Africa should be using a comma as a separator... even though none of us use this standard :D

So then it breaks our code, and our business rules that disagree with it.

Well, the good news is we can change the regional settings of the running thread, by changing its CultureInfo details. Here is a little utility class to do so:

public class TemporaryCultureSwitch : IDisposable
{
  private readonly CultureInfo _originalCulture;
  private readonly CultureInfo _originalUICulture;
  
  public TemporaryCultureSwitch(CultureInfo cultureInfo)
  {
    _originalCulture = Thread.CurrentThread.CurrentCulture;
    _originalUICulture = Thread.CurrentThread.CurrentUICulture;
    
    Thread.CurrentThread.CurrentCulture = cultureInfo;
    Thread.CurrentThread.CurrentUICulture = cultureInfo;
  }
  
  public TemporaryCultureSwitch(string cultureName) : this(new CultureInfo(cultureName)) { }
  
  public void Dispose()
  {
    Thread.CurrentThread.CurrentCulture = _originalCulture;
    Thread.CurrentThread.CurrentUICulture = _originalUICulture;
  }
}

We can now update our test to be a bit more specific:

[TestMethod]
public void FormatShouldNotBeAffectedByCultureChanges()
{
  var culture = new CultureInfo("en-ZA");
  culture.NumberFormat.NumberDecimalSeparator = ",";
  using (new TemporaryCultureSwitch(culture))
  {
    var sut = new ReportFormatter();
    
    var result = sut.Format(12345.67M);
    
    Assert.AreEqual("12345.67", result);
  }
}

Now we have a test that will fail consistently! Time to fix the code. One way of doing this is realizing that there is an overload of Decimal.ToString that takes in a CultureInfo object. We actually can use the InvariantCulture as below:

public string Format(decimal value)
{
  return value.ToString(CultureInfo.InvariantCulture);
}

The test passes and we now know for sure that regional settings won't affect our code.

Wednesday, January 25, 2017

Uninstall Windows Service using PowerShell

You can get a list of services using the Get-Service cmdlet, for example, to retrieve a list of all running services you could run:

Get-Service | Where-Object { $_.Status -eq "Running" }

You could even retrieve a specific service and start/stop it:

$service = Get-Service DemoService
$service.Start()
$service.Stop()

However, there is nothing to remove the service on this object.

We can also use GetWmiObject to do the same things:

$service = Get-WmiObject Win32_Service -Filter "name='DemoService'"
$service.StartService()
$service.StopService()

But this WMI object actually also exposes a delete functionality, which will mark the service to be deleted.

$service = Get-WmiObject Win32_Service -Filter "name='DemoService'"
$service.Delete()

If there are resources that are being held onto, it will only be deleted once they're freed up - whether it's the service that needs to gracefully shutdown still, or maybe the services manager snap-in (services.msc) has an open properties window open (I think that's happened to me before), and closing it then puts the delete through.

Tuesday, December 30, 2014

Finding the hostname for an IP address on Windows

When trying to investigate networking issues, most people know about ping and nslookup. I discovered today that there's also a utility called nbtstat to get some NetBIOS info, including the hostname and MAC address from an IP address.

nbtstat -a IPADDRESS

Tuesday, May 20, 2014

Getting the Event Log from another machine with PowerShell

If you have to manage a server farm of more than one computer, PowerShell can be of use if you have access to it.

If you don't have access to it, you may have to setup PowerShell Remoting (don't ask me how, because I haven't done that myself yet).

PS C:\> Get-EventLog -ComputerName somecomputer -LogName Application -Newest 10

Note that I'm using the -Newest paramter and not using the -After parameter, because apparently this means you will need to wait for PowerShell to go through the entire log, instead of stopping at a certain point.

Using a specific version of PowerShell

If you inspect $PSVersionTable you'll see a few different versions of things being used with PowerShell in the current session, specifically look for $PSVersionTable.PSVersion. By default the most latest version will be used.

To launch a specific version of PowerShell you can pass the -version switch, e.g. for PowerShell v2:

c:\ powershell -version 2