How to give liquid medication to cat? Giving Mythos Clavamox drops (Antibiotic for Gingivitis)

Mythos was put under anesthesia to clean his teeth few weeks ago at the animal hospital, vet had to take one of his decayed tooth out. After 2 weeks of recovery, we found that Mythos still got very bad breath. Our vet Dr. DeRose said it was probably caused by Gingivitis, he prescribed some Clavamox drops for him. For 2 times a day lasting 2-3 weeks, here is how I did it, not difficult! Way easier than giving pills. Now, most of the bad breath and red gums are gone! I guess Clavamox works, but not sure if Gingivitis will come back or not.

 

Posted in Cats (Mythos & Rini) | Tagged , , , , , , | Leave a comment

DIY Cat Gate V4.0

After using the DIY Cat Gate V3.0 for many years, it is almost time to upgrade. Recently we moved to a new house, so I re-used the old frame from Cat Gate v3.0 and upgraded to this version. Here is the YouTube video:

 
Same as before, the design is to minimize the damage to the house but it has to be structural stable. So, I anchored to the wall on both sides – yes I had to rip the drywall and added 2×4 for support but nothing is anchored on the hardwood floor (only felt pads are used). In case we need to sell the house, the only thing need to do is to patch the drywall, easy enough.

Posted in Cats (Mythos & Rini), DIY & Home Improvement | Tagged , , , , | Leave a comment

DIY Cat Tunnels / Doors / Pass Through between rooms

We want Mythos and Rini to enjoy running around on the 2nd floor, but we don’t want them to go downstairs (Cat Gate V4.0 is another project) because of various reasons. When all the doors are closed, there is no way for them go from one room to another. The solution is very simple – let them go through the walls.

If you have seen my other DIY projects, you know that I am structural first person. In order for Mythos and Rini to be absolutely safe (not trapped between drywalls), I had to build it between the studs. By putting 2×4 horizontally for the support, then adding the plywood to over the edge of all the dry walls. Finally finishing it with the nice trim. The only tricky part was that they have to go through our walk-in closet to the bedroom, so I re-used some of the wood (an old desk) to build a corner unit. That one looked a bit ugly, but it’s in the closet, it’s fine. The rest is pretty much self explanatory, enjoy.

Posted in Cats (Mythos & Rini), DIY & Home Improvement | Tagged , , , , , | Leave a comment

Amazon AWS Route 53 Dynamic IP Updater Windows Service

This article has been posted to CodeProject.com using Rini Boo name as usual, as I do not want my coworkers know about my secret life. For this application, I have been running on my home server for several months without memory leak. Any comments, suggestions are welcome.

Article is on CodeProject.com:
Amazon AWS Route 53 Dynamic IP Updater Windows Service

Introduction

I have been a long term paid customer for DynDns but Amazon Route 53 has better price and more flexibile monthly billing, not to mention I am migrating the VMs to AWS VPC/EC2 which triggers me to make the switch.  Route 53 is great but the problem is that I cannot find any Route 53 windows service client to update IP automatically,  I still want to access my windows based servers at home in a realiable way, but not relying on those free IP/DNS service. This is why I started this project.

Background

Initially the project was written for Aamzon Route 53 but the architecture and design is flexible enough for anyone to implement other DNS update providers. Visual Studio 2013 with .NET 4.5.0 is needed.

The key features are:

  • Multiple DNS providers, multiple domains update, not limited to Amazon Route 53 and you can implement other providers easily such as (DynDns, No-IP, etc.)
  • Multiple IP checkers, you can run your own IP checker on your ISP or Amazon VPC/EC2. When one fails it automatically goes to next one
  • Custom XML configuration for easy configuration
  • Detailed Logging for all activities (not limited to File logging), you can configure to write to Event Log easily with no code change (Microsoft Enterprise Library knowledge needed)
  • Notification by email (Gmail SMTP tested) for any updated made
  • Password encryption by Des3. This can be considered a weakness as symmetric encryption is not secure
  • Monitoring of DNS provider status change. DNS provider such as Rotute 53 update usually takes up to several minutes to propagate for all their DNS servers. AWS provides an API for checking the change status with a given ID. To ensure a change is successful, PENDING would be changed to INSYNC for completion.
  • Force Update in x days
  • Runs as Windows Service using Open Source TopShelf project
  • No user interface Smile | :)

Prerequisites
Visual Studio 2013 and .NET 4.5.0 was used.  The following packages are needed from NuGet restore:

  • Microsoft Enterprise Library 6.0.1304
  • Microsoft Unity 3.5.1404
  • AWS SDK 2.3.19
  • Topshelf 3.1.4

Architecture and Design
First of all, let’s talk about the configuration. To support multiple domains, multiple providers and multiple IpCheckers,  the default App.config key-value pair configuration is not flexible enough, therefore we have a custom XmlConfig.

XML Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<xmlconfig>
<domains>
    <domain>
      <domainname>home.yourdomain1.com</domainname>
      <providertype>AMAZON_ROUTE_53</providertype>
      <hostedzoneid>ZQ455PJ32KNK8GI2A</hostedzoneid>
      <accessid>AJUGYGYSNHYQHSJFOB</accessid>
      <secretkey>jFjF34lazdJFjdjm24FHsdjfg</secretkey>
      <minimalupdateintervalinminutes>5</minimalupdateintervalinminutes>
      <lastipaddress>1.1.1.1</lastipaddress>
      <lastupdateddatetime>2015-02-21T19:29:12.8381639Z</lastupdateddatetime>
      <changestatusid></changestatusid>
    </domain>
    <domain>
      <domainname>home.yourdomain2.com</domainname>
      <providertype>AMAZON_ROUTE_53</providertype>
      <hostedzoneid>ZJFD34IIFJ52SIF</hostedzoneid>
      <accessid>AJUGYGYSNHYQHSJFOB</accessid>
      <secretkey>jFjF34lazdJFjdjm24FHsdjfg</secretkey>
      <minimalupdateintervalinminutes>5</minimalupdateintervalinminutes>
      <lastipaddress>1.1.1.1</lastipaddress>
      <lastupdateddatetime>2015-02-21T19:29:12.8381639Z</lastupdateddatetime>
      <changestatusid></changestatusid>
    </domain>
  </domains>
 
  <providers>
    <provider>
      <providertype>AMAZON_ROUTE_53</providertype>
      <providerurl>https://route53.amazonaws.com</providerurl>
    </provider>
  </providers>
 
  <ipcheckers>
    <ipchecker>
      <ipcheckertype>CUSTOM</ipcheckertype>
      <clienttype>WEB_HTTP</clienttype>
      <ipcheckerurl>http://ec2.yourdomain1.com/</ipcheckerurl>
    </ipchecker>
    <ipchecker>
      <ipcheckertype>JSON_IP</ipcheckertype>
      <clienttype>WEB_HTTP</clienttype>
      <ipcheckerurl>http://www.jsonip.com/</ipcheckerurl>
    </ipchecker>
    <ipchecker>
      <ipcheckertype>DYN_DNS</ipcheckertype>
      <clienttype>WEB_HTTP</clienttype>
      <ipcheckerurl>http://checkip.dyndns.com/</ipcheckerurl>
    </ipchecker>
  </ipcheckers>
</xmlconfig>

 

To understand the design, you need the basic understanding of Dependency Injection (DI) and Inversion of control (IoC)  in software engineering.

 

Dependecy Injection with Unity IoC container

Because the configuration is so flexible, we cannot create the instance of the object until finish reading the XmlConfig during runtime,  DI is absolutely needed. Microsoft Unity IoC container is used to resolve the instance from different interfaces.

For the interfaces, we have "IDnsProvider", "IIpAddressChecker" and "IClient". 

IDnsProviders can be AmazonRoute53DnsProvider,  DynDnsProvider, NoIpDnsProvider, etc.
IIPAddressChecker can be CustomIpAddressChecker, DynDnsIpAddressChecker, JsonIpAddressChecker, etc.
IClient can be WebHttpClient, WebSslClient, WebSoapClient, etc.  (for IpAddress Checker to reuse)

1
2
3
4
5
public interface IDnsProvider
{
    string UpdateDns(string accessID, string secretKey, string providerUrl, string domainName, string hostZoneId, string newIPaddress);
    Meta.Enum.ChangeStatusType CheckUpdateStatus(string accessID, string secretKey, string providerUrl, string id);
}
1
2
3
4
public interface IIpAddressChecker
{       
      string GetCurrentIpAddress(string IpProviderURL, IClient client);     
}
1
2
3
4
public interface IClient
{
     string GetContent(string IpProviderUrl, DelegateParser parser);
}

Serialization and Object Mappings

To use the configuration,  we perform the deserialize the XML  into objects, but then flexibilty is needed to use it as in-memory objects contains all the child instances.  The goal is to have the workable objects that are decoupled  from the deserialized objects.   In this case, we make it simple by mapping the XmlConfig to 2 new models: "DomainModel" and "IpCheckerModel".  

In Multi-tier application, we often use open source AutoMapper for Data Transfer Object (DTO) but for this project, it is a bit overkill, so manual mapping was done.  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DomainModel
{
  public string DomainName { get; set; }
  public Meta.Enum.DnsProviderType DnsProviderType { get; set; }
  public IDnsProvider DnsProvider { get; set; }
  public string ProviderUrl { get; set; }     // Flatten as one of the properties
  public string HostedZoneId { get; set; }
  public string AccessID { get; set; }
  public string SecretKey { get; set; }
  public string MinimalUpdateIntervalInMinutes { get; set; }
  public string LastIpAddress { get; set; }
  public DateTime LastUpdatedDateTime { get; set; }
  public string ChangeStatusID { get; set; }     
}
1
2
3
4
5
6
7
8
public  class IpCheckerModel
{
  public Meta.Enum.IpCheckerType IpCheckerType { get; set; }
  public Meta.Enum.ClientType ClientType { get; set; }
  public string IpCheckerUrl { get; set; }
  public IClient Client { get; set; }
  public IIpAddressChecker IpAddressChecker { get; set; }
}

Run time resolving DnsProvider instance from XmlConfig and maps to DomainModel. Similar is done for IpChecker based on the configured type. I assume you got the idea already.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//.......
//.......
 
// Load Domains from Config, Resolve the DnsProvider instance based on configuration
_domainModelList = new List&lt;DomainModel&gt;();
foreach (Domain domain in xmlConfig.Domains)
{
    DomainModel domainModel = new DomainModel();
    domainModel.DomainName = domain.DomainName;
    domainModel.DnsProviderType = domain.ProviderType;
    // Resolve DnsProvider
    domainModel.DnsProvider = _container.Resolve&lt;IDnsProvider&gt;(domain.ProviderType.ToString());  
    // Find the matching Provider (XmlConfig object) and get the URL, flatten it for this Model                        
    domainModel.ProviderUrl = ((Provider)xmlConfig.Providers.FirstOrDefault(x =&gt; x.ProviderType == domain.ProviderType)).ProviderUrl;  
    domainModel.HostedZoneId = domain.HostedZoneId;
    domainModel.AccessID = domain.AccessID;
 
    // Decrypt the string if it the data is encrypted
    if (ConfigHelper.EnablePasswordEncryption)
        domainModel.SecretKey = Des3.Decrypt(domain.SecretKey, ConfigHelper.EncryptionKey);
    else
        domainModel.SecretKey = domain.SecretKey;
 
    domainModel.MinimalUpdateIntervalInMinutes = domain.MinimalUpdateIntervalInMinutes;
    domainModel.LastIpAddress = domain.LastIpAddress;
    domainModel.LastUpdatedDateTime = domain.LastUpdatedDateTime;
 
    if (domainModel.DnsProvider == null)
    {
        Logger.Write(&quot;Cannot resolve Provider, misconfiguration in XML config file in Domain section.&quot;, Meta.Enum.LogCategoryType.CONFIGURATION.ToString());
        throw new ArgumentNullException();
    }
 
    if (String.IsNullOrWhiteSpace(domain.ChangeStatusID))
        domainModel.ChangeStatusID = null;
    else
        domainModel.ChangeStatusID = domain.ChangeStatusID;
 
    _domainModelList.Add(domainModel);
}
 
// Load IpCheckers from Config, Resolve the instance
_ipCheckerModelList = new List&lt;IpCheckerModel&gt;();
foreach (IpChecker ipChecker in xmlConfig.IpCheckers)
{
    // Mapping from Config to Model
    IpCheckerModel model = new IpCheckerModel();
    model.IpCheckerType = ipChecker.IpCheckerType;
    model.IpCheckerUrl = ipChecker.IpCheckerUrl;
    model.ClientType = ipChecker.ClientType;
    // Resolve Client
    model.Client = _container.Resolve&lt;IClient&gt;(ipChecker.ClientType.ToString());      
    // Resolve IpAddressChecker
    model.IpAddressChecker = _container.Resolve&lt;IIpAddressChecker&gt;(ipChecker.IpCheckerType.ToString());  
 
    if ((model.Client == null) || (model.IpAddressChecker == null))
    {
        Logger.Write(&quot;Cannot resolve Checker or Client, misconfiguration in XML config file in IpChecker section.&quot;, Meta.Enum.LogCategoryType.CONFIGURATION.ToString());
        throw new ArgumentNullException();
    }
 
    _ipCheckerModelList.Add(model);
}
 
//.......
//.......

XPath vs Re-serialization (Debatable)

When the update operation is completed, we need to update the XML file, information includes    LastUpdatedDateTime, LastIpAddress  and ChangeStatusID are updated using XPath. But it is debatable as you can re-serialize the XML and save the whole document.             

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/// &lt;summary&gt;
/// Update LastIpAddress +  LastUpdatedDateTime in XmlConfig using XPath  (Called by Update Dns)
/// &lt;/summary&gt;
/// &lt;param name=&quot;domainName&quot;&gt;&lt;/param&gt;
/// &lt;param name=&quot;dateTimeinUTC&quot;&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static bool UpdateLastUpdatedInformation(string domainName, string ipAddress, DateTime dateTimeinUTC)
{
    // Use Xpath to update (debatable - not deserialize the xml?)
    XmlDocument xmlDocument = new XmlDocument();
    xmlDocument.Load(AppDomain.CurrentDomain.BaseDirectory + XmlConfigFileName);
    XmlNode root = xmlDocument.DocumentElement;
 
    // Find the matching domain name using Xpath and update the datetime
    XmlNode node = root.SelectSingleNode(&quot;//Domains/Domain[DomainName=\&quot;&quot; + domainName + &quot;\&quot;]&quot;);
 
    if (node != null)
    {
        node[&quot;LastIpAddress&quot;].InnerText = ipAddress;
        node[&quot;LastUpdatedDateTime&quot;].InnerText = dateTimeinUTC.ToString(&quot;o&quot;);    // UTC timestamp in ISO 8601 format
 
        // Need to use this to fix carriage return problem if InnerText is an empty string
        XmlWriterSettings settings = new XmlWriterSettings { Indent = true };               
        using (XmlWriter writer = XmlWriter.Create(AppDomain.CurrentDomain.BaseDirectory + XmlConfigFileName, settings))
        {
            xmlDocument.Save(writer);
        }
 
 
        return true;
    }
    else
        return false;
}

Implementation of DnsProviders and IpCheckers

Here comes the actual implementation of the core dns updating and ip checking function. In the future, say Microsoft Azure launches a new DNS service, you can implement MicrosoftAzureProvider : IDnsProvider easily. 

Let's focus on Route 53 for now, AWS SDK has some good example online for updating DNS,  the only problem is that they use an infinite WHILE loop and SLEEP to check the Status which I do not quite agree using in real production despite we know that the documentation is for educational purpose only.

Ref: http://docs.aws.amazon.com/AWSSdkDocsNET/latest/DeveloperGuide/route53-recordset.html

Therefore we have another method for CheckUpdateStatus which is called by a separated Timer. AmazonRoute53Provider class is designed to perform both DNS update as well as checking the status. Here is the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class AmazonRoute53DnsProvider : IDnsProvider
{
  // ........
  // ........
  // ... Constructor/variables not shown here (download source code)
  // ........
  // ........
 
public string UpdateDns(string accessID, string secretKey, string providerUrl, string domainName, string hostZoneId, string newIPaddress)
{
 
    string changeRequestId = null;
 
    // Assign parameters
    _accessID = accessID;
    _secretKey = secretKey;
    _providerUrl = providerUrl;
 
    // Create a resource record set change batch
    ResourceRecordSet recordSet = new ResourceRecordSet()
    {
        Name = domainName,
        TTL = 60,
        Type = RRType.A,
        ResourceRecords = new List&lt;ResourceRecord&gt; { new ResourceRecord { Value = newIPaddress } }
    };
 
    Change change1 = new Change()
    {
        ResourceRecordSet = recordSet,
        Action = ChangeAction.UPSERT        // Note: UPSERT is used
    };
 
    ChangeBatch changeBatch = new ChangeBatch()
    {
        Changes = new List&lt;Change&gt; { change1 }
    };
 
    // Update the zone&#39;s resource record sets
    ChangeResourceRecordSetsRequest recordsetRequest = new ChangeResourceRecordSetsRequest()
    {
        HostedZoneId = hostZoneId,
        ChangeBatch = changeBatch
    };
 
    ChangeResourceRecordSetsResponse recordsetResponse = AmazonRoute53Client.ChangeResourceRecordSets(recordsetRequest);
    changeRequestId = recordsetResponse.ChangeInfo.Id;
 
    return changeRequestId;
 
} // method
 
 
 
/// &lt;summary&gt;
///  AmazonRoute53 takes several minutes to propagate through all the DNS servers, Status is Pending after submit
/// &lt;/summary&gt;
/// &lt;param name=&quot;id&quot;&gt;&lt;/param&gt;
public Meta.Enum.ChangeStatusType CheckUpdateStatus(string accessID, string secretKey, string providerUrl, string id)
{
    if (String.IsNullOrEmpty(id))
        return Meta.Enum.ChangeStatusType.INSYNC;
 
    // Assign parameters
    _accessID = accessID;
    _secretKey = secretKey;
    _providerUrl = providerUrl;
 
    // Monitor the change status
    GetChangeRequest changeRequest = new GetChangeRequest(id);
 
    if (AmazonRoute53Client.GetChange(changeRequest).ChangeInfo.Status == ChangeStatus.PENDING)
        return Meta.Enum.ChangeStatusType.PENDING;
    else
        return Meta.Enum.ChangeStatusType.INSYNC;        
 
} // method
 
}

As for IpChecker, each IpAddressChecker can return a different format, so we need a parser to parse the HTML for checkip.dyndns.com or a Json parser to parse results from JsonIp.com. To reuse IClient, we pass the Parser as Delegate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/// &lt;summary&gt;
/// Web Http Client with Delegate Parser
/// &lt;/summary&gt;
public class WebHttpClient : IClient
{
    /// &lt;summary&gt;
    /// Get the content of the Html as string
    /// &lt;/summary&gt;
    /// &lt;param name=&quot;IpProviderUrl&quot;&gt;&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public string GetContent(string IpProviderUrl, DelegateParser parser)
    {
        string content = null;
        int timeoutInMilliSeconds = Convert.ToInt32(ConfigHelper.ClientTimeoutInMinutes) * 60 *1000;
 
        // Use IDisposable webclient to get the page of content of existing IP
        using (TimeoutWebClient client = new TimeoutWebClient((timeoutInMilliSeconds)))
        {
            content = client.DownloadString(IpProviderUrl);
        }
 
        if (content != null)
            return parser(content);
        else
            return null;
    }
 
    /// &lt;summary&gt;
    /// To support timeout value, alternatively you can use HttpWebRequest and Stream
    /// &lt;/summary&gt;
    public class TimeoutWebClient : WebClient
    {
        public int Timeout { get; set; }
 
        public TimeoutWebClient(int timeout)
        {
            Timeout = timeout;
        }
 
        protected override WebRequest GetWebRequest(Uri address)
        {
            WebRequest request = base.GetWebRequest(address);
            request.Timeout = Timeout;
            return request;
        }
    }
 
 
}

JsonIp.com is a great free service and it is very fast, it returns your current IP in JSON format, so we need to parse it. This the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class JsonIpAddressChecker : IIpAddressChecker
{
 
    public string GetCurrentIpAddress(string ipProviderURL, IClient client)
    {
        // Pass the parser as function to the client
        DelegateParser handler = Parse;
        return client.GetContent(ipProviderURL, handler);
 
    }
 
    private string Parse(string jsonString)
    {
        string ipString = null;
 
        // format: {&quot;ip&quot;:&quot;x.x.x.x&quot;,&quot;about&quot;:&quot;/about&quot;,&quot;Pro!&quot;:&quot;http://getjsonip.com&quot;}
 
        var jsonSerializer = new JavaScriptSerializer();
        Dictionary&lt;string, string&gt; data = jsonSerializer.Deserialize&lt;Dictionary&lt;string, string&gt;&gt;(jsonString);
        ipString = data[&quot;ip&quot;];
 
        // Validate if this is a valid IPV4 address
        if (IpHelper.IpAddressV4Validator(ipString))
            return ipString;
        else
            return null;
    }
 
}
 
public class DynDnsIpAddressChecker : IIpAddressChecker
{
  // Implementation (download source code)
}
 
public class CustomIpAddressChecker : IIpAddressChecker
{
  // Implementation (download source code)
}

If you have Amazon VPC/EC2 with IIS or you have a favorite ISP which runs traditional ASPX page, you can simply use the CustomIpAddressChecker and put this ASPX code as Default.aspx:

1
2
3
4
5
6
7
8
9
10
11
 
&lt;%@ Page Language=&quot;C#&quot; %&gt;
&lt;%
// In case your server is behind proxy
string ip = Request.ServerVariables[&quot;HTTP_X_FORWARDED_FOR&quot;];
 
// If not, then use the traditional remote_addr
if (ip == null) 
    ip = Request.ServerVariables[&quot;REMOTE_ADDR&quot;];
Response.Write(ip);
%&gt;

Time based Update and Monitoring Workflow

Amazon Route 53 is dfferent from other DNS updater provider, after you call the Route 53 API for an IP update, the status for that domain/sub-domain changes to PENDING and a change status ID is returned. It takes up to several minutes to propagate for all their DNS servers, but it is acceptable for updating home IP address.  Route 53 provides another API you can call to check the update status. When  the operation is completed, it returns INSYNC.

To perform a time-based update, either Thread or Timer can be used. Since an update can happen after another, there is no need to implement  concurrent threading. Two seperated Timers are used:

  1. First timer (For Update) runs every 5 min (default) to check the for update (calling IPCheckers and Parse the IP from the result), if it is less than MinimalUpdateIntervalInMinutes (per domain settings to prevent abuse) or IP has not been changed then continues.  Otherwise, calls the Dns Provider to perform an update to the current IP, then updates the in-memory objects as well as XmlConfig.xml for the LastIpAddress, LastUpdatedDateTime and ChangeStatusID.  Repeat for all the domains in the configurations.  
     
  2. Second timer (For Monitoring)  runs every 1 minute (default)  to check if the there is any ChangeStatusID needs to be monitored for all the domains.  When DNS Provider API returns INSYNC  (using Amazon terminology),  ChangeStatusID is set to empty and an email notification is sent out.

We define the core "Worker" class which contains the 2 timers, and we register all the type mappings in the Unity container. Then resolves them after reading the config (as explained above already).
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
public class Worker
{
    // Timer for interval update and monitor status
    private Timer _updateTimer;
    private Timer _montiorTimer;
 
    // IoC for flexbile configuration
    private IUnityContainer _container = new UnityContainer();
 
    // XmlConfig (XML serialization objects) maps to Model Collection
    private List&lt;IpCheckerModel&gt; _ipCheckerModelList;
    private List&lt;DomainModel&gt; _domainModelList;
 
 
    /// &lt;summary&gt;
    /// Constructor
    /// &lt;/summary&gt;
    public Worker()
    {
        try
        {
 
            // Init the enterprise log
            Logger.SetLogWriter(new LogWriterFactory().Create());
            Logger.Write(&quot;Updater worker Initialized.&quot;, Meta.Enum.LogCategoryType.WIN_SERVICE.ToString());
 
            // Mappings for Unity container
            _container.RegisterType&lt;IDnsProvider, AmazonRoute53DnsProvider&gt;(Meta.Enum.DnsProviderType.AMAZON_ROUTE_53.ToString());
            _container.RegisterType&lt;IClient, WebHttpClient&gt;(Meta.Enum.ClientType.WEB_HTTP.ToString());
            _container.RegisterType&lt;IIpAddressChecker, CustomIpAddressChecker&gt;(Meta.Enum.IpCheckerType.CUSTOM.ToString());
            _container.RegisterType&lt;IIpAddressChecker, DynDnsIpAddressChecker&gt;(Meta.Enum.IpCheckerType.DYN_DNS.ToString());
            _container.RegisterType&lt;IIpAddressChecker, JsonIpAddressChecker&gt;(Meta.Enum.IpCheckerType.JSON_IP.ToString());
            _container.RegisterType&lt;INotification, EmailNotification&gt;(Meta.Enum.NotificationType.EMAIL.ToString());
 
            // Read the XML config file for all the Domains/Providers/IpCheckers and Map them to Model
            MappingToModel(ConfigHelper.LoadConfig());
 
 
            // Configure the Timers and handlers
            _updateTimer = new Timer(Convert.ToDouble(ConfigHelper.UpdateIntervalInMinutes) * 1000 * 60);
            _updateTimer.Elapsed += new ElapsedEventHandler(UpdateTimerElapsed);
 
            _montiorTimer = new Timer(Convert.ToDouble(ConfigHelper.MonitorStatusInMinutes) * 1000 * 60);
            _montiorTimer.Elapsed += new ElapsedEventHandler(MonitorTimerElapsed);
 
        }
        catch (Exception ex)
        {
            Logger.Write(String.Format(&quot;FATAL ERROR, Exception={0}&quot;, ex.ToString()), Meta.Enum.LogCategoryType.WIN_SERVICE.ToString());
        }
 
    }
 
 
    /// &lt;summary&gt;
    /// Start the timer
    /// &lt;/summary&gt;
    public void Start()
    {
        _updateTimer.Start();
        _montiorTimer.Start();
        Logger.Write(&quot;Service has started.&quot;, Meta.Enum.LogCategoryType.WIN_SERVICE.ToString());
 
        // First time running it no waiting 
        this.RunUpdate();
    }
 
    /// &lt;summary&gt;
    /// Stop the timer
    /// &lt;/summary&gt;
    public void Stop()
    {
        _updateTimer.Stop();
        _montiorTimer.Stop();
        this.CleanUp();
        Logger.Write(&quot;Service has stopped.&quot;, Meta.Enum.LogCategoryType.WIN_SERVICE.ToString());
    }
 
 
 
    /// &lt;summary&gt;
    /// Event hook for the update job
    /// &lt;/summary&gt;
    private void UpdateTimerElapsed(object sender, ElapsedEventArgs e)
    {
        this.RunUpdate();
    }
 
 
    /// &lt;summary&gt;
    /// Monitor the update status after submit
    /// &lt;/summary&gt;
    private void MonitorTimerElapsed(object sender, ElapsedEventArgs e)
    {
        this.RunMonitor();
    }
 
 
 
    /// &lt;summary&gt;
    /// Loop through the domains and update the IP
    /// &lt;/summary&gt;
    public void RunUpdate()
    {        
        // ...............
        // ...............  Details Implementation (download source code)
        // ...............
    }
 
    /// &lt;summary&gt;
    /// Monitor the DNS update status after submitted to the provider
    /// &lt;/summary&gt;
    public void RunMonitor()
    {
        // ...............
        // ...............  Details Implementation (download source code)
        // ...............
 
    }
 
}

Windows Service and TopShelf

Open source TopShelf is used because it is a very good windows service framework, it makes code debuggable and makes the application more configuable as well as install.  It creates the new instance of Worker() and calls the Start() when the service is started and calls the Stop() when service is stopped. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Program
{
    public static void Main()
    {
        // Using Open Source project &quot;Topshelf&quot; to handle the run as windows service
        // Ref: http://topshelf-project.com/
 
        HostFactory.Run(x =&gt;
        {
            x.Service&lt;Worker&gt;(s =&gt;
            {
                s.ConstructUsing(name =&gt; new Worker());
                s.WhenStarted(tc =&gt; tc.Start());
                s.WhenStopped(tc =&gt; tc.Stop());
            });
            x.RunAsLocalSystem();
 
            x.SetDescription(&quot;Update current IP address supports multiple DNS providers&quot;);
            x.SetDisplayName(&quot;Dynamic DNS Updater Service&quot;);
            x.SetServiceName(&quot;DynamicDnsUpdater&quot;);
        });               
 
    } // main
 
} //  program

Sample Log file in Action

In this example, first we backdated the LastUpdatedDateTime more than 1 month on the XmlConfig,  launched the application and IP has been updated due to 30 days passed and ForceUpdate kicked in.  At round 10 minutes mark, we manually changed our IpChecker (on VPC/EC2) to 192.168.0.1 to simulate a change in IP.  If you take a closer look, on the first update 1 notification was set only. But on second update, 2 notifications were sent sepreately. The reason was the 1 minute monitoring interval,  Route 53 completed the change for the first domain but second domain was still PENDING.

2015-03-04 12:04:49 WIN_SERVICE -1 Updater worker Initialized.
2015-03-04 12:04:49 WIN_SERVICE -1 Service has started.
2015-03-04 12:04:50 IP_CHECKER -1 IpChecker=http://checkip.yourdomain.com/, IP=74.125.226.111
2015-03-04 12:04:50 DNS_UPDATE -1 Domain=home.yourdomain1.com - FORCE UPDATED provider successfully from IP=74.125.226.111 to IP=74.125.226.111 with ID=/change/C1XBHGFNFIHU8A, passed 30 days
2015-03-04 12:04:50 DNS_UPDATE -1 Domain=home.yourdomain1.com - UPDATED XML configuration LastIpAddress=74.125.226.111, LastUpdatedDateTime=3/4/2015 5:04:50 PM
2015-03-04 12:04:50 DNS_UPDATE -1 Domain=home.yourdomain2.com - FORCE UPDATED provider successfully from IP=74.125.226.111 to IP=74.125.226.111 with ID=/change/C4L2T7HJFSOI4B, passed 30 days
2015-03-04 12:04:50 DNS_UPDATE -1 Domain=home.yourdomain2.com - UPDATED XML configuration LastIpAddress=74.125.226.111, LastUpdatedDateTime=3/4/2015 5:04:50 PM
2015-03-04 12:05:49 STATUS_MONITOR -1 Domain=home.yourdomain1.com - ChangeStatus=PENDING
2015-03-04 12:05:50 STATUS_MONITOR -1 Domain=home.yourdomain2.com - ChangeStatus=PENDING
2015-03-04 12:06:50 STATUS_MONITOR -1 Domain=home.yourdomain1.com - XML configuration ChangeStatusType Updated to INSYNC
2015-03-04 12:06:50 STATUS_MONITOR -1 Domain=home.yourdomain2.com - XML configuration ChangeStatusType Updated to INSYNC
2015-03-04 12:06:53 NOTIFICATION -1 Notification has been sent successfully
2015-03-04 12:11:02 IP_CHECKER -1 IpChecker=http://checkip.yourdomain.com/, IP=74.125.226.111
2015-03-04 12:11:17 DNS_UPDATE -1 Domain=home.yourdomain1.com - NOT UPDATED because IP=74.125.226.111 has not been changed
2015-03-04 12:11:20 DNS_UPDATE -1 Domain=home.yourdomain2.com - NOT UPDATED because IP=74.125.226.111 has not been changed
2015-03-04 12:16:59 IP_CHECKER -1 IpChecker=http://checkip.yourdomain.com/, IP=192.168.0.1
2015-03-04 12:17:00 DNS_UPDATE -1 Domain=home.yourdomain1.com - UPDATED provider successfully from IP=74.125.226.111 to IP=192.168.0.1 with ID=/change/C47J0BHCS33NAD
2015-03-04 12:17:00 DNS_UPDATE -1 Domain=home.yourdomain1.com - UPDATED XML configuration LastIpAddress=192.168.0.1, LastUpdatedDateTime=3/4/2015 5:17:00 PM
2015-03-04 12:17:10 DNS_UPDATE -1 Domain=home.yourdomain2.com - UPDATED provider successfully from IP=74.125.226.111 to IP=192.168.0.1 with ID=/change/C5YDKABPC145AM
2015-03-04 12:17:10 DNS_UPDATE -1 Domain=home.yourdomain2.com - UPDATED XML configuration LastIpAddress=192.168.0.1, LastUpdatedDateTime=3/4/2015 5:17:10 PM
2015-03-04 12:17:55 STATUS_MONITOR -1 Domain=home.yourdomain1.com - ChangeStatus=PENDING
2015-03-04 12:17:55 STATUS_MONITOR -1 Domain=home.yourdomain2.com - ChangeStatus=PENDING
2015-03-04 12:18:55 STATUS_MONITOR -1 Domain=home.yourdomain1.com - XML configuration ChangeStatusType Updated to INSYNC
2015-03-04 12:18:55 STATUS_MONITOR -1 Domain=home.yourdomain2.com - ChangeStatus=PENDING
2015-03-04 12:18:57 NOTIFICATION -1 Notification has been sent successfully
2015-03-04 12:19:55 STATUS_MONITOR -1 Domain=home.yourdomain2.com - XML configuration ChangeStatusType Updated to INSYNC
2015-03-04 12:19:57 NOTIFICATION -1 Notification has been sent successfully
2015-03-04 12:22:52 IP_CHECKER -1 IpChecker=http://checkip.yourdomain.com/, IP=192.168.0.1
2015-03-04 12:22:52 DNS_UPDATE -1 Domain=home.yourdomain1.com - NOT UPDATED because IP=192.168.0.1 has not been changed
2015-03-04 12:22:53 DNS_UPDATE -1 Domain=home.yourdomain2.com - NOT UPDATED because IP=192.168.0.1 has not been changed

Important Notes

Production Notes:  If you ever manually edit the XmlConfig.xml file, you have to RESTART the windows service to take effect. Otherwise it will continue to use the in-memory data.

Debug Notes:  As I mentioned open source TopShelf is so good that you can actually launch the code in Visual Studio and debug the windows service!  Note that when you run in debug mode, it creates the   DynamicDnsUpdater.Service.config and XmlConfig.xml in the /bin/debug folder.  The application will NEVER change the App.config and Xml.Config.xml in your source folder. 

Compile and Installation

  1. Visual Studio 2013 – Enable NuGet manager to restore all the library packages, Compile
  2. In /BIN folder, delete all unnecessary files such as *.pdb, *.manifest, *.application (optional)
  3. Copy the files to the server such as C:\Program Files (x86)\DnsUpdaterService\
  4. On the server, make sure you have FULL version of .NET Framework 4.5 (Not client profile version)
  5. On the server prompt, type  > DynamicDnsUpdater.Service.exe install
  6. EDIT DynamicDnsUpdater.Service.config file (this is App.config) and change
  7.   – Notification email/username/password/smtp
  8.   – Logging Path:  c:\Logs\DnsUpdaterService.log and c:\Logs\DnsUpdaterError.log
  9. EDIT XmlConfig.xml (Domains/Providers config) and update your Domains/Providers
  10. GO to Windows Service,  look for the "Dynamic DNS Update Service", START.  

Encrypt Password/SecretKey

By default, smtp password in App.config and SecretKey in XmlConfig.xml are not encrypted, if you want you can encrypt the both by using the default DES3 encryption method.  DES3 is symmetric, therefore it is not very secure but it's better than nothing.  To do this:

  1. You can set the flag "EnablePasswordEncryption" to true in App.config
  2. Update your own encryption key in ConfigHelper.EncryptionKey
  3. Use Des3.Encrypt() to encrypt your plaintext Password/SecretKey

Tighten IAM Security on AWS

You may want to tighten the security of the user created in IAM (Identity and Access Management), because by default when creating a user, it may assign Power User group which can access everything in your AWS account (VPC/EC2, etc.) In order to limit the user to have access to Route 53 particular zone only, edit your IAM user to the following policy below. In this example, ZQ455PJ32KNK8GI2A is the Hosted Zone ID for my first domain, ZJFD34IIFJ52SIF is for my second domain.  This is just a simple JSON document and it is self explanatory. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  "Version": "2012-10-17",
  "Statement":[
      {
         "Action":[
            "route53:ChangeResourceRecordSets",
            "route53:GetHostedZone",
            "route53:ListResourceRecordSets",
            "route53:GetChange" 
         ],
         "Effect":"Allow",
         "Resource":[
            "arn:aws:route53:::hostedzone/ZQ455PJ32KNK8GI2A",
            "arn:aws:route53:::hostedzone/ZJFD34IIFJ52SIF",
            "arn:aws:route53:::change/*"
         ]
      },
      {
         "Action":[
            "route53:ListHostedZones"
         ],
         "Effect":"Allow",
         "Resource":[
            "*"
         ]
      }
   ]
}

At this point of writing the article, Amazon AWS DOES NOT support domain/sub-domain name level security, it only supports zone level security. In order words, IAM user is able to change everything within a zone. 

I hope you find article useful!  

View, Download or Contribute at GitHub: https://github.com/riniboo/DynamicDnsUpdater.Service

Download the source code (ZIP file)
DynamicDnsUpdater.Service.zip (Right-click, Save Target As)

Posted in Technology & Dev | Tagged , , , , , , , , , , , | Leave a comment

Hong Kong and Japan (九州: Fukuoka 福岡 + 由布院)

Hong Kong was my birth place,  I do support the idea behind the Umbrella Movement to fight for freedom. But this time of visit made me feel like Hong Kong was not the same place used to be anymore, especially with the HK-mainland conflict,  first person experienced the rudeness of certain people made me really sick.

Japan is a very beautiful country, people are so polite and clean. We arrived in Fukuoka and rented a car in to drive to YuFu, Oita.  Being in North America for many years, it was a bit weird to drive on the RIGHT-hand-drive, but I was able to manage it without issues. 由布院 is one of my favorite place.

Posted in Photography, Travel | Tagged , , | Leave a comment

How to reset “Away (x minutes)” in Microsoft Lync 2013 with Windows 8.1? (Securely Slacking Updated)

Problem Recap: In the office, I want to step out for 2.5 hours but I don’t want other people to know. At the same time, I want to lock my workstation for security reason (important requirement). With Microsoft Lync being the primary communicating tool in our office, the annoying feature “Away (x minutes/hours)” really bugs me when the workstation is locked.

Note: This is an update of the AutoIt script to work in Microsoft Lync 2013 and Windows 8/8.1. Please read the following 2 articles before reading this one.

Technical Details: Unlike Windows 7 or previous version of windows, Windows 8 or 8.1 does not require CTRL-ALT-DEL anymore to prompt the log-in screen! There are several cases once the workstation is locked (1) Show wall paper only, (2) Showing YOUR existing login, (3) Showing multiple users. The new AutoIt script simply uses the [ESC], [SPACE] and [BACKSPACE] keys to do the trick and should be able to get around all scenarios. Also, we found that Lync 2013 didn’t change your status from Away to Online if just unlocking the workstation, however movement of a mouse plus a left-click did the trick. We have incorporated that into the updated script.

AutoIt_w8_003

Configuration and Usage:

Read the first article (links above) first, instead of using VMware you can use Hyper-V that comes with Windows 8/8.1 for free. Then install an old Windows 7 on the VM.

On VNC Server (host OS), Under “Administration” Tab, select “Lock Desktop” When last client disconnects. We use VNC to re-lock the workstation, this feautre is avaiable in newer version of VNC server.

It is recommended to use Microsoft Virutal Machine Connection instead of Remote Desktop Connection. During testing, RDP is not as stable as using Hyper-V VM Connection for some reasons. Also, please minimize the VM Window, move it to the back with other windows on top, or move it to the extended desktop (2nd monitor if you have one) because once VNC is connected, infinite windows can create problems.

Tested on:

  • Windows 8.1 Enterprise 64-bit with Hyper-V (host system)
  • Lync 2013 (Version 15.0.4659.1001) on host OS
  • Windows 7 Professional on Hyper-V (client system)
  • AutoIt 3.3.10.0 on client OS

Download: LyncResetAwayStatusWin8.1.au3 (Right-click, Save Target As)

Source Code: (The script is totally free to use/distribute as long as you keep the copyright line)

; Auto Login to computer to get around "Away XXX minutes" in Microsoft Lync
;
; Usage: Pretend to be at desk preventing to show maximum XXX away time
;        You can have 2 hours lunch but people always see you are away for maximum 25 minutes
;
; Configuration: On VNC Server (host OS), Under "Administration" Tab, select "Lock Desktop" When last client disconnects.
;                We use VNC to re-lock the workstation, this feautre is avaiable in newer version of VNC server.
;
; Note : It is recommended to use Microsoft Virutal Machine Connection instead of Remote Desktop Connection. During testing,
;        RDP is not as stable as using Hyper-V VM Connection for some reasons.  Also, please minimize the VM Window, move it to
;        the back with other windows on top, or move it to the extended desktop (2nd monitor if you have one) because once
;        VNC is connected, infinite windows can create problems.
 
; Required:
; (1) AutoIt Version: 3.3.8.1+
; (2) tightvnc-2.7.10-setup-64bit.msi  (64 bit) - Full Installation on host and VncViewer only on client
 
; Tested on: Windows 8.1 Enterprise 64-bit with Hyper-V (host system)
;            Lync 2013 (Version 15.0.4659.1001) on host OS
;            Windows 7 Professional on Hyper-V (client system)
;            AutoIt 3.3.10.0 on client OS
;
 
; Written and Copyrighted by: Mythos and Rini
; Created on: 2013-03-08 (For Windows 7, read original blog article)
; Update:  2013-04-29 (Adding sleep to multiple places otherwise it does not always work)
; Update:  2014-10-29 (Adding mouse move after login before re-lock + Tested it on Windows 8.1)
 
; Reference
; Jason More - Unlock the computer using VNC
; ref: http://www.autoitscript.com/forum/topic/71333-solution-to-computer-locked-screen-ctrl-alt-del-scripting/
 
; Different TightVNC version behaves differently, for example "vncviewer.exe" vs "tvnviewer.exe" and the title
; "Standard VNC Authentication" vs "Vnc Authentication" (case sensitive). So please use the SAME VNC
; version as stated above to eliminate any potential issues, or you have to modify the script manually.
;
 
#include <Array.au3>
 
; This script requires full Administrative rights
#requireadmin
 
; The configuration based on Tight VNC 2.7.10 64-bit installation
Dim $vncPath = "C:\Program Files\TightVNC"
Dim $vncViewerExe = "C:\Program Files\TightVNC\tvnviewer.exe"
Dim $vncNewConnectionWindowName = "New TightVNC Connection"
Dim $vncAuthWindowName = "Vnc Authentication"
 
; Change the VNC host/port accordingly
 
Dim $vncServerHostPort = "172.11.22.33:40"
Dim $vncPassword = "f@@kwork"
 
; Windows password (do not need username)
Dim $windowsPassword = "p@ssw0rd"
 
; 60000 = 60 sec = 1 min.  sleep(60000 * 25) = 25 min
; This unlock the computer every 25 mins (it shows maximum 25 min on Lync)
; Dim $delay = 60000 * 25
 
Dim $delay = 60000 * 25
Dim $repeat = 5
 
;Dim $delay = 10000    ; 5 seconds for debug
;Dim $repeat = 1      ; run once for debug
 
Dim $i = 0
 
; Loop forever until being stopped manually
While $i < $repeat
 
	  ; Script Start
 
	  sleep($delay)
 
	  ; Slowing down the key presses helps with the connection
	  opt("SendKeyDelay",10)
 
	  ; This should point to your tight vnc installation
	  run($vncViewerExe,$vncPath)
 
	  ; Wait for the new connection windows to come up
	  winwait($vncNewConnectionWindowName)
	  controlsend($vncNewConnectionWindowName ,"","Edit1", $vncServerHostPort)
	  controlclick($vncNewConnectionWindowName ,"","Button4")
 
	  ; Wait for the authenication window and send password
	  winwait($vncAuthWindowName)
	  controlsend($vncAuthWindowName,"","Edit2", $vncPassword)
	  controlclick($vncAuthWindowName,"","Button1")
 
	  ; Sleep for 2 seconds
	  sleep(2000)
 
      ; Use AutoIt WindowsInfo or Microsoft Spy++ to get the class name
	  $VncClassName="[CLASS:TvnWindowClass]"
 
		; Get all the active/minimized VNC windows
		$winList = WinList($VncClassName)
 
		For $a = 0 to $winList[0][0]
 
		   ; Activate Communicator window if there is any
		   If WinActivate($winList[$a][0]) Then
 
			  ; Get title of VNC
			  $title = WinGetTitle($winList[$a][0], "")
 
			 ; Debug
			 ; MsgBox(0, "Title", $title)
 
			  ; Unlike Windows 7 or previous version of windows, Windows 8 or 8.1 does not require CTRL-ALT-DEL anymore!
			  ; There are several cases (1) Show wall paper only,  (2) Showing YOUR existing login, (3) Showing multiple users
			  ; The following combination should be able to get around all scenarios.
			  ; Note: DO NOT get rid of the sleep delay, it needs that for CPU/HDD/NIC delay
 
			  controlsend($title,"","TvnWindowClass1","{ESC}")
			  sleep (2000)
			  controlsend($title,"","TvnWindowClass1","{SPACE}")
			  sleep (2000)
			  controlsend($title,"","TvnWindowClass1","{BACKSPACE}")
			  sleep (2000)
			  controlsend($title,"","TvnWindowClass1","{BACKSPACE}")
			  sleep (2000)
			  controlsend($title,"","TvnWindowClass1", $windowsPassword)
			  sleep (5000)
			  controlsend($title,"","TvnWindowClass1", "{ENTER}")
			  sleep (5000)
 
			  ; Random coordinates
			  $x=Random(0,800)
			  $y=Random(0,600)
			  MouseMove($x, $y, 5)
			  MouseClick("left")
 
			  ; Close the VNC viewer
			  sleep(8000)
			  WinClose($title)
		   ENdIf
 
 
		Next
 
		$i = $i + 1
 WEnd
Posted in Technology & Dev | Tagged , , , , , | Leave a comment

[Cat Circle 貓圈實驗‬] How to trap a cat – Will that work on British Shorthair? (In Chinese & English)

Recently, there have been many of articles and videos on the Internet about “How to trap a cat” in a circle, Google “Cat Circle” for more information. So we decided to do the same experiment on Mythos and Rini. The result is….

Posted in Cats (Mythos & Rini) | Tagged , , | Leave a comment

Windows 2012 R2 Windows Backup – Hyper-V .Vhdx restore manually and run it on another machine

Recently I have upgraded my home server to Windows 2012 R2 which runs Hyper-V, after converting all my old Windows 2008 images from .VHD to .VHDX then I tried the Windows Server Backup. It is actually pretty good since it uses volume shadow copy and there is no downtime on all the VMs. The question is, can I restore the Windows Backup files manually? Let me put it to the test and restore the image and run it on my Windows 8 desktop.

Setup:

  • Hyper-V: Windows Server 2012 R2
  • VM image: Windows 7 (called “Atlas”)
  • Desktop: Windows 8 (for testing to see if I can mount it and run the image on desktop)
  1. Windows Server backup created a GUID .vhdx on my Qnap NAS called “16ceb1f0-6d10-4843-a45a-96b7594aacb2.vhdx” (64 GB). If you try to mount this .vhdx directly to Hyper-V, it will fail and it will not boot.
  2. I copied that GUID .vhdx file to my Windows 8 desktop, but when I clicked on it or tried to mount it like an ISO file, it gave me an error message
  3. Go to Disk Management, assign Drive “E”
  4. Look for the image on Drive E and found the real image “Atlas.vhdx”
  5. Created a new VM on Windows 8 desktop and mount the .vhdx file on Drive E
  6. Bingo, VM started successfully
Posted in Technology & Dev | Tagged , , , | Leave a comment

Simple Mistake – Dynamically load images into multiple HTML Canvas

I haven’t written Javascript for a while as I have been focusing on the backend stuff in the past years. This is supposed to be a very simple task but took me some time to figure out. Indeed, coming from a strongly typed language like Java and C#. The Javascript .onload() event syntax actually threw me off a bit. Let’s examine.

This is HTML:

1
2
3
<canvas id="ImageCanvas0" />
<canvas id="ImageCanvas1" />
<canvas id="ImageCanvas2" />

This is Javascript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
$(document).ready(function () {
    renderImage();
});
 
function renderImage() {
    for (var i = 0; i < 3; i++) {
 
        var canvas = $('#ImageCanvas' + (i));
        var context = canvas[0].getContext('2d');
 
        var imageObj = new Image();
 
        imageObj.onload = function () {
            context.drawImage(imageObj, 0, 0);
        };
         imageObj.src = "/ImageRepository/?id=" + i;
    }
}

By reading the Javascript above, there seems nothing is wrong. Indeed the script was working partially without error, but just the result wasn’t expected. The ImageCanvas0 and ImageCanvas1 were blank but ImageCanvas2 was actually loaded properly.

Why? It’s because imageObj was treated like a global variable. Okay, let’s fix it by having 2 Arrays for Canvas and Image and then pass the ID. Simple fix, right?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function renderImage() {
    var CanvasContextList = new Array(3);
    var ImageList = new Array(3);
 
    for (var i = 0; i < 3; i++) {
 
        var canvas = $('#ImageCanvas' + (i));
        var context = canvas[0].getContext('2d');
        CanvasContextList[i] = context;
 
        ImageList[i] = new Image();
        ImageList[i].onload = function () {
            CanvasContextList[i].drawImage(ImageList[i], 0, 0);    // Problem: undefined in Array[3]
        };
 
         ImageList[i].src = "/ImageRepository/?id=" + i;
    }
}

Not really, Javascript is throwing undefined error. Looking at the debugger and traced the variable, interestingly when the FIRST onload happened, the variable “i” was actually having the value “3”. Of course, it is undefined.

Onload is multi-threaded and it is triggered by .src. When the function (event) is executed, it looks for the variable “i” which was last used by the for-loop having the value “3”.

Now, this is the final solution and it worked.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function renderImage() {
    var CanvasContextList = new Array(3);
    var ImageList = new Array(3);
 
    for (var i = 0; i < 3; i++) {
 
        var canvas = $('#ImageCanvas' + (i));
        var context = canvas[0].getContext('2d');
        CanvasContextList[i] = context;
 
        ImageList[i] = new Image();
        ImageList[i].id = i;                  // Assigning an ID to Image object
        ImageList[i].onload = function () {
            CanvasContextList[this.id].drawImage(ImageList[this.id], 0, 0);   // ID is used
        };
 
        ImageList[i].src = "/ImageRepository/?id=" + i;
    }
}

Simple mistake, I guess I have to write more Javascript. Hope this helps other people.

Posted in Technology & Dev | Tagged , , | Leave a comment

Lorex LNR200 series – How to get snapshot still image for your web site and custom C# streaming video from NVR?

I have been using Geovision card and analog cameras for home security for 10 years and it is time to upgrade to HD. I have been using Lorex analog cameras for many years because of its quality therefore I decided to buy Lorex LNR280 with 8 IP cameras (LNB2153 + LNB2152). I am amazed by the PoE + HD 1080p although 2 cameras were not working properly (night vision couldn’t see anything despite the infrared LEDs were working) and I had to replace them via RMA. Customer service was not too bad.

Lorex is a very smart company, not only it uses Costco as sales channel, they indeed not to spend money on R&D for these IP cams and NVR. They just “white-label” a company called HikVision‘s products and of course firmware was developed in mainland China. If you view the HTML code on NVR, you can find simplified Chinese. LOL.

Lorex Viewing – Admin web site + Mobile App Security Concerns

First of all, Lorex admin web site is using ActiveX control. If you are using Proxy (at work), the video streaming will not work. On the mobile side, the HDNet app. is great but it requires you to open multiple ports 8000 + 1025 publicly on the router and most importantly, data is not encrypted.

My solution to get around it is to use SSH + Putty (On Windows Desktop) and SSH + ConnectBot (On Android), none of those ports have to be opened. Well, this is a separated topic for advanced users.

Analysis of NVR and video streaming using RTSP

If you look at the configuration and documentation of Lorex, there are information about 3 ports: 80, 8000 and 1025. Port 80 is the administration web site, 8000 is remote data admin port, 1025 is RTSP video streaming (H264).

I am interested in RTSP. Looking at the black box, I need to find the URL. Fiddler guided me to a javascript called /doc/script/preview.js and I could find out the RTSP URL from the code. (see photo). Then I used VLC Media Player (it’s open source free to download) I could actually stream the video on VLC player! Bingo!

Lorex_LNR200_LNR280_0001

Lorex_LNR200_LNR280_0002

Assuming your LAN is on 192.xx.xx.xx and NVR is on your LAN:

rtsp://user:pass@192.xx.xx.xx:1025/PSIA/streaming/channels/101 (cam#1)
rtsp://user:pass@192.xx.xx.xx:1025/PSIA/streaming/channels/201 (cam#2)
rtsp://user:pass@192.xx.xx.xx:1025/PSIA/streaming/channels/301 (cam#3)
….

Proof of Concept: on using C# and .NET for streaming

Download:

1. vlc-2.1.3-win32.exe (Install it)
2. VideoLan DotNet for WinForm, WPF, SL5

I am using Visual Studo 2013 for the proof of concept. Follow the documentation to initialize the VLC context in your Program.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
 
            //Set libvlc.dll and libvlccore.dll directory path
            VlcContext.LibVlcDllsPath = CommonStrings.LIBVLC_DLLS_PATH_DEFAULT_VALUE_AMD64;
            //Set the vlc plugins directory path
            VlcContext.LibVlcPluginsPath = CommonStrings.PLUGINS_PATH_DEFAULT_VALUE_AMD64;
 
            //Set the startup options
            VlcContext.StartupOptions.IgnoreConfig = true;
            VlcContext.StartupOptions.LogOptions.LogInFile = true;
            VlcContext.StartupOptions.LogOptions.ShowLoggerConsole = true;
            VlcContext.StartupOptions.LogOptions.Verbosity = VlcLogVerbosities.Debug;
 
            //Initialize the VlcContext
            VlcContext.Initialize();
 
            Application.Run(new Form1());
 
            //Close the VlcContext
            VlcContext.CloseAll();
 
        }

Then, simply add the 3 DLLS to your project reference and also add the Vlc.DotNet.Forms.dll to your Toolbox (for the form designer). After dragging the control to your Form, a vlcControl1 instance should be created. After that, add 3 buttons. Edit your Form.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 
        public Form1()
        {
            InitializeComponent();
        }
 
        private void button1_Click(object sender, EventArgs e)
        {
            string path = "rtsp://username:password@192.168.xxx.xxx:1025/PSIA/streaming/channels/701";
 
            LocationMedia media = new LocationMedia(path);
 
            vlcControl1.Media = media;
            vlcControl1.Play();
 
        }
 
        private void button2_Click(object sender, EventArgs e)
        {
            vlcControl1.Stop();
        }
 
        private void button3_Click(object sender, EventArgs e)
        {
            string outputFile = "C:\\capture.jpg";
            vlcControl1.TakeSnapshot(outputFile, 1920, 1080);
        }

Although there are some errors on the command prompt logs, but the video was actually streaming properly. Even screen capture was working as you can see from the photos. The JPG captured is in HD 1920 x 1080.

Lorex_LNR200_LNR280_0003

Lorex_LNR200_LNR280_0004

Playback on certain time (Recorded video)

This example is to play back on Cam#1, from 2014-05-27 0:0:0 to 2014-05-28 23:59:59

rtsp://username:password@192.168.xxx.xxx:1025/PSIA/streaming/tracks/101?starttime=20140527T000000Z&endtime=20140528T235959Z (cam#1)

This is working perfectly on VLC player.

Getting single still image from Lorex LNR200 (LNR280)

Finally getting still image is something I need to do for my custom home automation and mobile app., just like my previous Geovision Hack. If you are business owner, you can use this to post the live images on your web site. Getting the image is extremely simple

http://192.168.xxx.xxx/PSIA/Streaming/channels/101/picture (cam#1)
http://192.168.xxx.xxx/PSIA/Streaming/channels/201/picture (cam#2)
http://192.168.xxx.xxx/PSIA/Streaming/channels/301/picture (cam#3)
…..

There are 2 streams for each channel, for cam#1: Main-Stream 101 (1920×1080) and Sub-Stream 102 (704×576). However, I am keep getting 704 x 576 as resolution (wide screen with blank top/bottom) which is not ideal, I am not sure if it is a bug or not.

According to the HikVision documention, this should work:

  • /Streaming/channels/101/picture?videoResolutionWidth=1920&videoResolutionHeight=1080
    (Empty respond in ResponseStatus XML)
  • /Streaming/channels/101/picture?snapShotImageType=JPEG
    (Invalid Operation in ResponseStatus XML)

But obviously they are not working on Lorex.

Anyways, 704×576 is kind of acceptable and it uses Basic Authentication, so you can embed your username/password in the URL and get the picture on your browser for testing:

http://username:password@192.168.xxx.xxx/PSIA/Streaming/channels/101/picture

To publish it on your site, this is a simple proof of concept (not using PHP this time), I am using traditional ASP.NET and MVC. For Web Form, this is your Image.aspx.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Clear();
        Response.ContentType = "image/jpeg";
 
        Uri uri = new Uri("http://192.168.xxx.xxx/PSIA/Streaming/channels/701/picture");  // cam#7
        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
 
        webRequest.Credentials = new NetworkCredential("username", "password");  // authenication
 
        HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
        StreamReader reader = new StreamReader(webResponse.GetResponseStream());
 
        var bytes = default(byte[]);
        using (var memstream = new MemoryStream())
        {
            reader.BaseStream.CopyTo(memstream);
            bytes = memstream.ToArray();
        }
 
        Response.BinaryWrite(bytes);         
    }

This is your Defualt.aspx or Index.html:

1
2
3
 
<h3>This is your web page, demo image from Lorex LNR280</h3>
<img src="./Image.aspx" style="width:350px; height:300px" />

As for ASP.NET MVC, Create a ImageController.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 
 public class ImageController : Controller
    {
        //
        // GET: /Image/
        public ActionResult Index()
        {
            Response.Clear();
            Response.ContentType = "image/jpeg";
 
            Uri uri = new Uri("http://192.168.xxx.xxx/PSIA/Streaming/channels/701/picture");  // cam#7
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);
 
            webRequest.Credentials = new NetworkCredential("username", "password");  // authenication
 
            HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
            StreamReader reader = new StreamReader(webResponse.GetResponseStream());
 
            var bytes = default(byte[]);
            using (var memstream = new MemoryStream())
            {
                reader.BaseStream.CopyTo(memstream);
                bytes = memstream.ToArray();
            }
 
            Response.BinaryWrite(bytes);     
 
            return View();
        }
	}

This is your Razor Home page Index.cshtml:

1
2
3
4
5
6
@{
    ViewBag.Title = "Home Page";
}
 
<h3>This is your web page, demo image from Lorex LNR280</h3>
<img src="/Image/Index" style="width:350px; height:300px" />

In one of the photos in this article, Fiddler complained about a HTTP protocol error!! What? In order to bypass that, add the following to the web.config (applies to both Web Form and MVC:

1
2
3
4
5
  <system.net>
    <settings>
      <httpWebRequest useUnsafeHeaderParsing="true" />
    </settings>
  </system.net>

Lorex_LNR200_LNR280_0005

Lorex_LNR200_LNR280_0006

Other discoveries + Full Hikvision API

The architecture of Lorex/Hikvision NVR is using simple REST web services, it uses HTTP GET/POST/PUT/DELETE to do all operation in plain XML. Nothing fancy, no encryption, no serialization (since it is not object-oriented). All the logic are handled on client side by javascript and Jquery. I used Fiddler and Firebug to do the reverse engineering, there are some URLs that you can use it as API:

/PSIA/Custom/SelfExt/ContentMgmt/DynVideo/inputs/channels
/PSIA/Custom/SelfExt/ContentMgmt/DynVideo/inputs/channels/status
/PSIA/Custom/SelfExt/OSD/channels/1/capabilities (cam#1)
/PSIA/Custom/SelfExt/OSD/channels/1 (cam#1)
/PSIA/Custom/SelfExt/ContentMgmt/DynVideo/inputs/channels/1 (cam#1)
/PSIA/System/deviceInfo (cam#1)
/PSIA/Custom/SelfExt/UPnP/ports/status (cam#1)
/PSIA/Custom/SelfExt/ContentMgmt/DynStreaming/channels
/PSIA/ContentMgmt/search/
/PSIA/Streaming/channels/101/capabilities (cam#1)
……
……

HikVision is different from Lorex, either firmware version or maybe Lorex has disabled some features on the NVR. Reference:

You will be amazed by the API available to the advanced users. However, many of them don’t work on Lorex NVR. But maybe it will work on the camera directly, not sure.

If you have a POE switch or use an power adapter, you can use your laptop directly connect to the admin port (8000) of the individual camera at 172.168.1.xxx (note: you need to switch your NIC from 192. to 172.) instead of connecting to the NVR. You can download a Hikvision iVMS-4200 PCNVR v1.02.00.03 (Windows) to change advance settings on the camera. I haven’t tried that, not sure if it will work or not.

Lorex vs HikVision

Take a look at the FAQ and Driver/SDK web pages of both HikVision and Lorex, compare them by yourself. Hikvision makes Lorex look like shit for advanced users. Hikvision wins hands down!! But if you are non-technical users, Lorex is pretty good. If I knew HikVision earlier, I wouldn’t be that stupid to go with Lorex.

Have fun with your Lorex 200 series. This article is based on my Lorex LNR280 firmware V2.2.3 build 130412.

Posted in Technology & Dev | Tagged , , , , , , , , , , | 5 Comments