Tools to run an IT Company Update

So, 8 months since the original post; https://tickett.wordpress.com/2017/09/28/tools-to-run-an-it-company/ let’s see what’s changed…

Our newest laptops are all HP Probook 640 G1; The fourth generation and last to supersede the Elitebook 8440P whilst still fitting the HP docking stations we are using. Sooner or later we will have to bite the bullet and get some newer generation and replace some docking stations.

The Xerox Phaser 6121MFP finally reached the end of days and got upgraded to a Xerox WorkCentre 6515N. Offering much the same features but much newer with more RAM, a faster processor and a few nice bonuses; Active Directory (LDAP) integration and duplex scanning/printing.

Version Control

Our development team have now moved from SVN to GIT. We are using GitLab Community Edition (hosted on-premise). I hope to share some of our practices and customisations soon. We feel GIT provides a more natural integration with Visual Studio and handles our collaboration better.

Support / Helpdesk / Ticket System

Over the Christmas period we wrote our own in-house helpdesk system which better fits our business. This continues to evolve each month and we hope to market it sometime in the future.

There should be a reasonable cost saving moving away from Solarwindows WebHelpDesk but we will be investing a large amount in developing the product and the biggest gains will be realised by the productivity improvements and enforced process/workflows.

Project Management

Once our in-house helpdesk system was live we quickly started work adding project managements features and moved away from Trello and Freedcamp. This will now prevent a lot of duplication previously keeping the disparate systems in sync.

We have now built a simple web based password manager with full and access control which we hope to market in the near future.

Internet Service Provider

After about 18 months of trying, Virgin Media Business has now been installed. The service is significantly poorer than I expected. Whilst we do often manage to achieve 100mb/s+ down and consistently 15mb/s up, the latency is poor (around 30ms) and the way they deliver static IP addresses is ridiculous (and forces you to use their router for the weird gre tunnel, you will need a minimum of 5 static IP addresses, the single IP will be assigned to the router itself and cannot be assigned to your own hardware).

NetXMS Agent to Server Connection (Tunnel)

We are now in a position where we want to monitor remote nodes (outside of our domain/LAN). Initially we were able to setup port forwarding on the remote network to allow traffic IN from our server to the agent on port 4700 (remembering to configure the windows firewall too). Then we hit a limitation with NetXMS server not allowing multiple nodes configured with the same ip/hostname (hopefully this will be resolved in a future release).

So we tried to flip the configuration so the remote agent connects in to our server. This proved a little challenging so i’ve documented how we got it working and some of the troubleshooting tips.

Enabling Logging

On the server server modify netxmsd.conf (on our server this was located at C:\NetXmsServer\etc). On the agent modify nxagentd.conf (on our agent this was found in C:\NetXMS\etc). Add;

LogFile = C:\path\to\log\log.txt
DebugLevel = 9


Then restart the NetXMS Core service (on the server) or NetXMS Agent service (on the agent).

**Make sure you change it back to 1 or remove it completely when you are done as the log file will grow very large, very quickly**

Creating the CA / Server Certificate

We wanted to do this with the bare minimum (most of the guides online were pages long). I was surprised there wasn’t an online tool to generate the certificate (perhaps I will build one sometime).

openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 10240 -out ca.pem


When prompted for Country, State, Organization etc we just hit enter to accept the default value. We then need to combine the key and certificate (we just used Notepad++)

-----BEGIN CERTIFICATE-----
MIIDYDCCAkigAwIBAgIJAPmfsLYXzwABMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgwNjA2MTI1OTUyWhcNNDYwNjE5MTI1OTUyWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAnRDoS4m4YV4ePquEUkmSHMIr40mgtJIFzTj4bIZ8UySaZpfw1LLaXUVP
eck0xZM5q4GnOS8fVQfzZv5uwKs76cy3VLl/kJlBnyFSRdZsLnbqTJI7LT5qOkK7
C5ryDNymA+6UcvBGmq/+hyht4zFwM3GB0EA/BfpmpiEtyzOu327VjCWh5LWGa1E5
QLsEEVzQ87fgmyQz89vxxCyejODxU5HcLdo62BN38LytZhiQPI366KOAvrS/k44y
RcbeYhJbHIqGPXaBsbswHwYDVR0jBBgwFoAU77bOpRE5RcbeYhJbHIqGPXaBsbsw
DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAY+Vt/zwrMS8l5B+D
zuZ3awRKe2olD8zmLlxcKNbj5csf9ivCCjHc8WWmuF0wqkMwTst7caNSdVjQozV0
UNUBVe1X3fQ8o2qRutwnT4FqYSEuD+nzXEdj+n7opJKqiNBMkvJzimIUlEoQwdJB
X6LDrJaF5WCtcbX57CvkFrLo8p9efM1pWi62q4oynp+Uyr4mEWMzMT82mnz2mbV/
Djio7aZ5nh6g0453tEhaQXmAWvDdbfReIy/TnPG7RiHoUWuBigE2pEB8HXdrYP7o
+lHIuQUhH2Ah4obIJxy48DokMriNL8rv7rj1pEpVzg4au+Nk6K0CFx5T/c1xuHud
CrBTrw==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAnRDoS4m4YV4ePquEUkmSHMIr40mgtJIFzTj4bIZ8UySaZpfw
1LLaXUVPeck0xZM5q4GnOS8fVQfzZv5uwKs76cy3VLl/kJlBnyFSRdZsLnbqTJI7
LT5qOkK7C5ryDNymA+6UcvBGmq/+hyht4zFwM3GB0EA/BfpmpiEtyzOu327VjCWh
9zH5tekaQLsEEVzQ87fgmyQz89vxxCyejODxU5HcLdo62BN38LytZhiQPI366KOA
vrS/k44ySDNa7oinvv2cE3X6T3s49P05QfcT4QIDAQABAoIBAQCV7JafzBT4tBtQ
nO3QkjNrShkUeqpw0lXBYBkZloHD51OOjIgwr4xqrYdAa8P4uCC7SiYyGn3g75bx
//n95TVqZaO1tt9CSlfuKqNa0GMxMwdREeB0vngtppbebAeSKWg6ppiC4kGy1D20
C+5giEdeg4FzJHBEJ5GmEFOi/HF3CdjynZkp0Exyur0NyFBNzt/5SA3g1/UVHu/l
FzxwzYhL5no8WagQyWzStMs577GkerFebv7vFqJhz/hrk8YZCT7WIZS+RW6xJKl5
ICClDColpOojPkEnEPzxnp8+RjKFjf29Z/hIF+Pd86gSyzDk+aH5kSOcXjuyyh/o
eOk762RJTwOxVARfycvpgCS1MGReULtOU9vBFBdrKGblTxiusgu3q7/8RVREj3Ot
XP4Eqq3wHIQfAPS8d3iQs18OQ8vMnKkyredMkm4UhXjE6VXyFpUMhRotAoGBAMfn
gqZijKhmLtXWlYpNYIw6xPhGpVaH/dhGSoFc8XEcayC9Jt+BPhMt1U8YACNernwi
m5cR5z0Bh8Uld1gPPWNyMOVfGeuVMlY2y6vFOWLywXn+K1PJwyZkc0JnEg5uw5CN
C5w1hKDlk1D1ri1OrccmNkUfj0DEzsKvk2qrY3UFAoGAP1fwf98SXfGJku0m+wi5
pLIugJ2HkLgAa9aATb5fAUwSDVK2FojvMzQHCFYItPcKlwhPW5G+W14jrafXYlWK
fMAZkT83NWxVy3I8+F2EAVUCgYBI4sFBFxd/4tnav8WASpwUJO1iNKfd9/F25JIP
vqqNejYZFr5QqZfPeFuY3gBW5ROrURb3quBr3t345KMx6USRuLXZO6aAvcI/qvTx
qRQ2Xppqd2CmyR6tJdzcbiYoOvx0/OgpyV2VKH9g+mLKRDCaNvV8mHgrvlZAp7GR
J/I4XQKBgQCLkeHDNRO15TjqBBfTxzZFjoq7lYoj966QVhFONpG+seXsbGcswR2m
nVb1zInlKcpH9sSh7juMlOHvLKlCEwyPqNb00VBQH7XD4CgjdjsJ3kdaM57p0CFN
gRIb8XxxG6v5Q7AtgKYXyTqB4nn7lIPh/C4fuC5vXk2PdIKLGsukEw==
-----END RSA PRIVATE KEY-----



Make sure you have a blank line at the end of the file (and the certificate comes before the private key). You can download the pre-built certificate here https://tickett.net/downloads/netxms_ca.pem

Configuring the Certificate

• Drop the ca.pem file anywhere on the NetXMS server
• Modify netxmsd.conf and add the following lines;
ServerCACertificate = C:\NetXmsServer\etc\cert\ca.pem
ServerCertificate = C:\NetXmsServer\etc\cert\ca.pem


Notice both the CACertificate and Certifcate path point to the same file. I don’t think the password is needed at all.

• Restart the NetXMS Core service

Creating a Tunnel / Adding a WAN Node

• Ensure your router is configured to forward traffic received on port 4703 to your NetXMS server
• Ensure your NetXMS server firewall is configured to allow traffic in on port 4703
ServerConnection = server_wan_fqdn_or_ip

• Restart NetXMS Agent service
• In the NetXMS server, look under Configuration, Agent Tunnel Manager
• You should see a red entry (if you don’t, something above went wrong)
• Right click and select Create node and bind
• Name your node and enter 0.0.0.0 for the ip/hostname and save (if the tunnel vanishes at this point, something above went wrong)

Good luck!

Aligning Fixed Width Strings in C#

We have built a reporting tool for the Clover ePos which creates a text report for printing using the built-in receipt printer. The app has been through numerous iterations and the code which specifically handles alignment has always bugged me. The task seems trivial to the human mind, but when trying to code seems to throw a lot of curve balls! Here are some examples of the anticipated behavior;

So we revisited it, and I hope have now built a simpler “one size fits all” (not exactly, but considerably less conditional than before). The code accepts a string with pipe separators to determine alignment (refer to the diagram above for a bit more info) and spits out a string padded accordingly;

public static string Align(string key, int width)
{
string newKey = "";
string[] keyElements = key.Split('|');

if (keyElements.Length == 1)
{
newKey += key;
}
else if (keyElements.Length == 2)
{
//left align the first chunk
newKey += keyElements[0];
//insert blank spaces so the entire line length will be a multiple of width
newKey += new string('.', width - ((key.Length - 1) % width));
//insert the last chunk
newKey += keyElements[1];
}
else if (keyElements.Length == 3)
{
if (keyElements[0] != "")
{
//left align the first chunk
newKey += keyElements[0];
//and pad the rest of the line
newKey += new string('.', width - (keyElements[0].Length % width));
}
double sidePadding = width - (keyElements[1].Length % width);

if (keyElements[2] != "")
{
newKey += new string('.', width - (keyElements[2].Length % width));
//and right align the last chunk
newKey += keyElements[2];
}
}

return newKey;
}


The receipt printer automatically wraps the string but we use a small method to split the string into chunks and add carriage returns/new lines for ease of use. Initially, this too was cumbersome (with loops and ifs etc), but we found a more elegant solution (avoiding re-inventing the wheel and reducing the number of lines of code- though I appreciate a loop is still occurring under the hood);

        public static string Wrap(string key, int width)
{
return Regex.Replace(key, ".{" + width + "}", "\$0" + Environment.NewLine);
}


Here’s some code we used to test the method(s);

        static void Main(string[] args)
{
int width = 33;

List keys = new List();
keys.Add("This first part is really long|quick");
keys.Add("The centre segment will always sit on it's own line|quick|brown");

foreach (string key in keys)
{
Console.WriteLine(new string('=', width));
Console.WriteLine("Input: " + key);
Console.WriteLine(new string('-', width));
string aligned = Align(key, width);
string wrapped = Wrap(aligned, width);
Console.WriteLine(wrapped);
}