Check Point Harmony Connect VPN and Cisco Jabber with DNS

If you’re using Check Point Harmony which is based on WireGuard or any VPN for that matter, you may not want Cisco Jabber from connected through the tunnel. Check Point shared that SIP is known not to work through Harmony Connect.

There is plenty of documentation from Cisco regarding how the Jabber client determines if its running from external or internal.

  1. Jabber does a DNS query for the SRV record
  2. If that record isn’t found, it will do another DNS query for the SRV record
  3. If that record isn’t found, it will do another DNS query for the SRV record

By knowing the records and sequence we can configure Check Point Harmony and DNS server resolve the correct address to the client to access it externally. These instructions assume you have Cisco Expressway Mobile Remote Access (MRA) setup and working and the appropriate DNS records in place externally.

Configuration – Harmony Connect

  1. In the Check Point Harmony Connect portal, under Settings > Harmony Connect App add the FQDN of your expressway to the bypass destinations (Ex.
  2. Under Settings > Corporate DNS Servers add an external DNS server such as Google ( or Cloudflare (
  3. In the Resolve the following domains section, record your settings and then remove them. The Harmony Connect client processes the domain resolution from top to bottom.
  4. Add the FQDN of your external expressway server and choose the external DNS server you added in Step 2. (Ex.
  5. Now add the domain names you removed previously in Step 3. (Note, this tells the Harmony Client that when it needs to resolve the expressway external address that its using a public DNS instead of your internal DNS).

Configuration – Windows DNS Server

You should have at least one Check Point Harmony connector running in Docker within your data center. Obtain the IP address of the docker container/host as this is where the DNS queries are being generated from, not from the client itself.

  1. Open Powershell on the DNS server and run the following commands
  2. Add-DnsServerClientSubnet -Name CheckPointHarmony -IPv4Subnet x.x.x.x/32 (Note: adjust the subnet or mask to only capture the IP of the Docker container(s). It is fine if the Docker container is running on a subnet with other servers.)
  3. Add-DnsServerQueryResolutionPolicy -Name “DirectCiscoJabberExternal” -Action IGNORE -ClientSubnet “EQ,CheckPointHarmony” -Fqdn “EQ,” -QType “EQ,SRV” (Note: this blocks the first SRV DNS query from the “subnet” we just created in Step 2)
  4. Add-DnsServerQueryResolutionPolicy -Name “DirectCiscoJabberExternal2” -Action IGNORE -ClientSubnet “EQ,CheckPointHarmony” -Fqdn “EQ,” -QType “EQ,SRV” (Note: this blocks the second SRV DNS query from the “subnet” we just created in Step 2)\
  5. Repeat these on any other internal DNS servers you have configured for Harmony Connect.

Now if you suspend your Harmony Connect client, reactivate it and exit/restart Cisco Jabber, everything should work as intended. Keep in mind, that if you have configured on your internal DNS server, that should not be there and is only meant for external DNS resolution.

Check Point Harmony Connect VPN and Cisco Jabber with DNS Read More »

PointClickCare – Single Sign On (SSO) with Active Directory Federation Services (ADFS)

Below is a step by step walk through on how to configure PointClickCare SSO with ADFS. PointClickCare will provide you with metadata XML files in which you will use to build the relying party trust. These files are unable to be imported into ADFS and we must pull the information from them manually. You will create one relying party and claim rules for each Single Sign On Endpoint (Web App, POC, eMar, Mobile App)

Gather Relying Party details from XML file

EntityID – Within the md:EntityDescriptor element will be entityID. Record the value within the quotes (Ex:″

Assertion Consumer URL – Within the md:AssertionConsumerService element will be Location. Record the value within the quotes (Ex:

Signature Certificate – Find <ds:X509Data><ds:X509Certificate>. Copy the value between those two XML elements and</ds:X509Data></ds:X509Certificate>. Paste the value into notepad and save it as a .cer file. Make a note of this expiration date as the certificate will need to be updated on the relying party configuration when PointClickCare renews it.

Create the Relying Party Trust

Open Active Directory Federation Services, go to Relying Party Trusts and click Add Relying Party Trust

Choose Claims aware

Choose Enter data about the relying party manually

Enter a name for the relying party trust. Here are some recommended names depending on the relying party trust.
PointClickCare – Web App
PointClickCare – POC App
PointClickCare – eMar App
PointClickCare – Mobile App

Don’t add a token encryption certificate, just click Next.

Check the box Enable support for SAML 2.0 WebSSO protocol and enter the Assertion Consumer URL you recorded from the XML file.

Add the EntityID that you recorded from the XML file and add it to the list of Relying party trust identifiers

If you plan to use an access policy, select the appropriate one or choose Permit everyone

Click Next on the review page

Uncheck Configure claims issuance policy for this application and click Close. We will do this later

Right click on the new Relying Party Trust and click Properties

Go to the Signature tab and click Add. Browser to the .cer certificate file you created in the beginning and then click Apply

Go to the Endpoints tab and click on the SAML Assertion Consumer Endpoint and click Edit. Check the box Set the trusted URL as default and click OK.

As of this writing, PointClickCare doesn’t offer Single Log Out with ADFS so we will configure it within ADFS. Click Add SAML on the Endpoints tab.

Change Endpoint type to SAML Logout and Binding to Redirect.
For the Trusted URL enter your ADFS signout URL (Ex:
Leave Response URL blank and click OK

Click OK on the Relying Party properties page.

Create the claims

Right click the Relying Party Trust again and choose Edit Claim Issuance Policy

We are going to add a total of 6 claims. Pay careful attention to the following directions

Click Add Rule and choose Send Claims using a Custom Rule

Name the claim rule SSO Key. Enter the custom rule

=> issue(Type = "ssoKey", Value = "YOURSSOKEY_FROM_PCC");

Click Finish/OK and click Add Rule and choose Send Claims using a Custom Rule

Name the claim rule Org Code. Enter the custom rule

=> issue(Type = "orgCode", Value = "YOUR_PCC_ORG_CODE");

Click Finish/OK and click Add Rule and choose Send Claims using a Custom Rule

Name the claim rule Module and enter the custom rule

=> issue(Type = "destmodule", Value = "MODULE");

PointClickCare Web App = PCC_WEB
PointClickCare Point Of Care (POC) = POC
PointClickCare eMar = EMAR

(Note: This claim is not used if you’re setting up the Mobile relying party)

Click Finish/OK and click Add Rule and choose claim rule Send LDAP Attributes as Claims.

Name the claim rule Username and select the attribute store Active Directory

IMPORTANT: The LDAP field that you’re about to select must be a one to one match to the PointClickCare Login Name field for each user. Most probably use the sAMAccountName, which means they log into a computer on your domain with the same username as they log into PointClickCare. If this is a new PointClickCare environment or you’re leveraging UAP you could also use a custom attribute in LDAP. Please note, this LDAP attribute has nothing to do with what the end user logins into the computer with (although it could be one in the same), it is only used to tell PointClickCare who the user is.

Select the LDAP Attribute that contains the unique identifier between Active Directory and PointClickCare. On the Outgoing Claim Type enter username all lowercase with no spaces.

Click Finish/OK and click Add Rule and choose claim rule Send Claims using a Custom Rule

(Note: This claim is not used if you’re setting up the Mobile relying party)

Name the rule Get Username for Subject and enter the following claim below. If you are not using sAMAccountName, change the two references below to the LDAP attribute you’re using.

c:[Type == "", Issuer == "AD AUTHORITY"]
=> add(store = "Active Directory", types = ("temp:sAMAccountName), query = ";sAMAccountName;{0}", param = c.Value);

Click Finish/OK and click Add Rule and choose claim rule Send Claims using a Custom Rule

Name the rule Create Subject with Username and enter the following claim below. Again, if you’re using a different LDAP attribute, change the name of the temp value to match. Relpace YOUR_ORG_CODE with your PointClickCare organization code. Be sure to keep the period at the end of the org code.

c:[Type == "temp:sAMAccountName"]
=> issue(Type = "", Value = "YOUR_ORG_CODE." + c.Value);

Click Finish/OK

The next claim is only used for the Mobile relying party.

Click Add Rule and choose claim rule Send Claims using a Custom Rule. Name the claim rule Org ID and enter the following rule below. Replace the value with the Org ID provided by PointClickCare for Mobile

=> issue(Type = "orgId", Value = "XXXXXXXX");

Click Finish/OK. You’ve finished the setup for the relying parties.

Validating ADFS Configuration

PointClickCare’s SSO works with an IDP Iniated setup which require changing/verifying two settings on the ADFS servers. On your ADFS server, open Powershell and run the following command. If either of them say false, you’ll need to enable them. You will need to restart the ADFS service for these to take effect.

To Verify
Get-AdfsProperties | Select EnableIdpInitiatedSignonPage, RelayStateForIdpInitiatedSignOnEnabled
To Set
Set-AdfsProperties -EnableRelayStateForIdpInitiatedSignOn $true
Set-AdfsProperties -EnableIdPInitiatedSignonPage $true

Creating the shortcut to use SSO

We will build the URL’s necessary for your users to access PointClickCare using SSO. In the previous step we enabled relay state, which allows the ADFS server to identify the relying party within the URL and provide a streamlined experience for your users.

Obtain your Idp initiated signon URL. It should be You can browse to this page and see your relying parties and test the SSO with PointClickCare.

Obtain the relying party identifier for the shortcut you’d like to make. For example, if creating a shortcut for Point Click Care Web App the identifier should be

Finally the last url you need is the target and that is

Head over to and enter the information you just gathered and general the URL. You can now visit that URL and once authenticated it will log you directly into PointClickCare.

PointClickCare – Single Sign On (SSO) with Active Directory Federation Services (ADFS) Read More »

Custom Motorized Roller Shades

Recently we had a pavilion built in our back yard but the in the late afternoon prevented us from enjoying it. We decided to purchase roller shades but I knew, like everything else, we needed to control it from our phones. Looking into motorized shades, they are extremely expensive and we are too froogle to buy them. After doing some research I felt confident that I could build them myself.

Home Depot sells Coolaroo roller shades that were perfect. We purchased a 10′ and 8′ for our needs.

There are many roller shade motors available on the internet. We found the motors from Rollerhouse which had a many options, rechargable battery motors, 120v, 12v DC and 24v DC. Originally I measured the tubing on the Coolaroo shade and purchased one that I thought would fit, knowing that the tube didn’t look like the ones on Amazon. Unfortunately, it didn’t work because the channel where the fabric is held is just big enough that it prevents the motor from sliding in.

Coolaroo roller shade tube 1″ 1/16
Coolaroo roller shade tube fabric channel

I was dissapointed but encouraged to build my own with the Coolaroo fabric. I purchased 1 1/4″ EMT tubing from Lowes (Home Depot didn’t have 1 1/4″ in stock. I purchased two 10′ sections as I had a 10′ and 8′ need.

I also purchased two of these 1 1/2′ roller shade motors from Rollerhouse on Amazon. (DC24V remote control and bracket). I also purchased one of these 24V DC power supply to control both roller shade motors.

When you get the roller shade motors, you’ll notice the rubberized circle piece will be too large to get into the tube. Use a dremel to remove the rubber only until you reveal the aluminum inside it. Keep testing the piece as you want it to enter easily but not have much or any play in it. Try to keep it uniform while you’re using the dremel. This is the wheel that makes contact with the inside of the EMT tubing and rolls the shade.

Original rubberized wheel before modification
Modified wheel showing the aluminum revealed
Modified wheel showing uniform adjustment

Next, use a razor blade and remove the small strip of rubber on the end piece. This will allow it to slide into the EMT tubing.

Small strip of rubber that needs to be removed on all sides.

Now confirm the motor can slide into the EMT tubing with minimimal movement. Next, measure from the black rubber piece towards the wire on the motor (where the EMT tubing makes contact) to the rubber wheel you used the dremel tool on. Record the measurement in the middle of the rubber wheel. This is to identify where we will put screws in to hold the wheel in place and ultimately turn the EMT tubing.

Measuring 10.5″

Measure your existing tubing and cut the EMT tubing to size with a cut off wheel. If you don’t have an existing tubing, measure your fabric layed down (without stretching) and add 1/4-1/2″ to each side. This will allow the shade material some room in case its not hung perfectly level or you don’t apply it to the tube perfectly.

Next, use a Sharpie marker and draw a perfectly straight line down the EMT tubing. I used aluminum angle from Home Depot to get a perfectly straight line. Next, I used carpet doubled sided tape from home depot and ran it down the EMT tubing, about 1/8″ away from the line I drew. Unfortunately, I couldn’t find this on Home Depot’s web site, but it was in the paint section and said it was for carpet. It’s about 1-1.5″ and had mesh inside it, and very sticky. Gorilla also makes ones but I didn’t try it.

Next, take your current shade and unravel it from the tube and remove it from the channel. You can also use your own fabric. With the black line facing up on the EMT tubing, take the fabric and place it on the doubled sided tape with the fabric just touching the black line. Be cautious if your fabric stretches, you do not want to pull/stretch the fabric when applying it to the double sided tape. Press the fabric firmly into the doubled sided tape.

Arrows indicating the black line and fabric following it perfectly leaving gap on both sides between end of tubing and fabric.

Next, take your measurement of the motor to the rubberized circle and make two dots on your tubing. Mark one dot right near the line (not through the fabric) and another one directly on the opposite side. Insert your roller shade motor into the tubing and use #8 x 1/2″ self tapping screws and insert them into the two dots. This will secure the rubberized circle in the EMT tubing and allow is to all work.

Showing the self tapping screw near the fabric
Showing both screws, directly opposite of each other.

Now follow the directions on mounting, pairing the remote and setting the up/down limit. Lastly, I purchased the BroadLink RM4 PRO IR/RF universal remote. The roller shade motors come with RF remotes that work on 433Mhz and the BroadLink allows me to learn the RF commands and integrate them into Home Assistant.

Lastly, Home Assistant already has an integration with BroadLink. Follow the instructions to learn the commands through Home Assistant. I then used a template cover to create the roller shade entity.

  - platform: template
      pool_pavilion_shade_front: #Your Entity Name
        friendly_name: "Pavilion Front Shade" #Friendly Entity Name
        unique_id: "PavillionFrontShade" #UniqueID so you can edit it within the UI and assign to an area
        device_class: shade
        position_template: 50 #work around so both up and down buttons are always enabled
          service: remote.send_command #service to leverage to raise the shade up
            device: PoolShadeFront #device name used when learning the RF command
            command: Up #command name when learning the RF command
            hold_secs: 2
            num_repeats: 2
            delay_secs: 0.4  
            entity_id: remote.broadlink_pro_remote #entity name of BroadLink RM4 Pro
          service: remote.send_command  #service to leverage to lowerthe shade down
            device: PoolShadeFront #device name used when learning the RF command
            command: Down #command name when learning the RF command
            hold_secs: 2
            num_repeats: 2
            delay_secs: 0.4
            entity_id: remote.broadlink_pro_remote #entity name of BroadLink RM4 Pro
          service: remote.send_command  #service to leverage to stop the shade
            device: PoolShadeFront #device name used when learning the RF command
            command: Stop #command name when learning the RF command
            hold_secs: 2
            num_repeats: 2
            delay_secs: 0.4
            entity_id: remote.broadlink_pro_remote #entity name of BroadLink RM4 Pro
      #####If you have a second shade#######
        unique_id: "PavillionSideShade"
        device_class: shade
        position_template: 50
          service: remote.send_command
            device: PoolShadeSide #device name used when learning the RF command
            command: Up #command name when learning the RF command
            hold_secs: 2
            num_repeats: 2
            delay_secs: 0.4  
            entity_id: remote.broadlink_pro_remote
          service: remote.send_command
            device: PoolShadeSide #device name used when learning the RF command
            command: Down #command name when learning the RF command
            hold_secs: 2
            num_repeats: 2
            delay_secs: 0.4
            entity_id: remote.broadlink_pro_remote
          service: remote.send_command
            device: PoolShadeSide #device name used when learning the RF command
            command: Stop #command name when learning the RF command
            hold_secs: 2
            num_repeats: 2
            delay_secs: 0.4
            entity_id: remote.broadlink_pro_remote

Custom Motorized Roller Shades Read More »

Managing Inbound Email – Strategies to focus on the important stuff

Do you find that you’re missing emails or you don’t see them until much later? I often get questions on ways to better manage emails to focus on critical ones. You probably get hundreds or maybe even thousands of emails a day, intermingled with solicitations, spam and legitimate emails you need to act on. Many email clients have the capability of creating rules and effectively creating rules will allow you to be notified of the important ones while reviewing the non-important later.

The primary goal of your rules is to allow all the critical/important emails to go into your Inbox while the non-important emails go to sub-folders to review later, whether it be once a day, once a week or never. By doing this, email on your phone or tablet will also show, at a glance, those important emails.

Don’t create a folders for departments or people.

Often times I see 50 different folders with co-workers names or various departments with the thought that they can easily find emails all from a person or from a department. When using this approach, you need to constantly check each email folder for a new email, even with the unread badge count next to the folder. You will quickly learn you’re not responding to or managing emails effectively. Leverage the built in search or column sort feature of your email client to get all the emails together to easily find the one you’re looking for.

Don’t use the setting “Mark As Read” or other client side rules.

You may be tempted to use the Mark As Read option in the rule set but this is a client side rule, meaning the rule will only run when the email client (Outlook) is open. If your computer is off or your email client is closed, the rule will not process until you open the email client. If you don’t receive email on a phone or tablet, this likely won’t have a big impact on you.

Do create folders for “notification” emails.

Email clients are extremely flexible to allow a very specific set of criteria for Sender, Recipient, Subject, Message and Message Header (the message header is information about the email such as where it came from, who it was for, what mail server it went through, etc).

You can create a rule to say any email from [email protected] to go to a folder. Let’s say you receive important emails from this email address but also a lot of notice emails. Do the notices all have a certain keyword in the subject or message body? Pretend the subject line has the word “Alert” in it. You can add a second criteria to your rule to say emails from [email protected] and subject contains “Alert”. This means that the email must come from that email address and anywhere in the subject line have the word “Alert”.

To take this one step further, let’s imagine that you want emails from [email protected] with the word “Warning” in the subject line to stay in your inbox and not hit this rule, and the subject line has the word “Alert” in it as well. By using the exception portion of a rule, you can create the rule to say

From Sender: [email protected]
Subject Contains: Alert
Subject Contains: Warning

Create a rule to look for the word “unsubscribe” in the email body and redirect it to a folder.

Most marketing and solicitation emails have to contain a method for the user to be able to easily remove themselves from a mailing list. This is the CAN-SPAM law enforced Federal Trade Commission (FTC).

Keep in mind that if a co-worker receives an email that contains the word “unsubscribe” and forward it to you to review, this rule will also catch it. You may want to also add an Exception to this rule to look for message headers that contain your email domain name.

Continually monitor the folders that your rules are putting emails into to be sure they are setup correctly. Adjust them as necessary by adding additional clarifying criteria or exceptions.

Managing Inbound Email – Strategies to focus on the important stuff Read More »

Email Phishing Mitigation Technique

Phishing and credential leaks have always been difficuilt to block with technology that’s currently available. Relying on email spam filters might reduce the volume to a point but are likely noticing some still come through. While all of the mitigation techniques you have will offer protection, user education and simulated phishing emails are extremely important too.

One mitigation technique is to create a rule and route emails for approval coming from the outside your domain that contain attachments with the following file extensions .html, .htm, .aspx, .asp, .shtml, .zip and .one. As you monitor the emails to approve you can add exceptions to your rule. Some secure email systems, like Cisco, send a html attachment for to view the secure email.

Phishers are getting creative and they often embed your domain name in the link of the URL so when the unsuspecting employee clicks on it, it will show your domain name on the web site. This way they can send a mass phishing campaign and don’t need to change their code. You can create a rule and route emails for approval from outside your domain that contain your domain name within the href tag of the link. Add the rule that matches the pattern in the message subject or body to be: <a [^>]*\bhref\s*=\s*”[^”]*YOURDOMAINNAME.*?<\/a>

Have other suggestions, leave a comment!

Email Phishing Mitigation Technique Read More »

Veeam – License managed by Enterprise Manager

If you installed Veeam Enterprise Manager and uninstalled it at some point, you may notice Backup and Replication console complains that the license cannot be updated because its managed by Enterprise Manager. You can resolve this by making a simple update to a table within the SQL database.

First run the select command: SELECT * FROM Options WHERE [Name] = 'EnterpriseServerInfo'

Copy the XML and changed the XML element <IsConnected>True</IsConnected> to <IsConnected>False</IsConnected. Then run the update statement to adjust it. Remember to keep the other XML elements when you run the update statement.

UPDATE Options set VALUE = '<EnterpriseServerInfo> <IsConnected>False</IsConnected> <ServerName>BackupServerName</ServerName> <Url>https://BackupServerName:9443/</Url> <SkipLicensePush>False</SkipLicensePush> </EnterpriseServerInfo>' WHERE [Name] = 'EnterpriseServerInfo'

Veeam – License managed by Enterprise Manager Read More »

Duo Universal C# ASP NET Web Forms

Duo recently released their new Universal two factor authentication method which changes from an iFrame method to using a redirect. Duo provides a C# example using .NET Core and MVC but if you still have old web forms applications you’ve built and aren’t ready, you can still integrate them with Duo Universal.

First, you’ll need to buil the DuoUniversal.dll binary from the project and add it as a reference to your project. You’ll also need to make sure you’re running .NET Framework 4.7.1 or higher for your project.

Second, add the following items from Nuget to the project

  • System.Text.Json
  • System.Net.Http.Json
  • Microsoft.IdentityModel.Tokens
  • Microsoft.IdentityModel.JsonWebTokens

Now you’re ready to start integrating it into your current project.

  1. On your login page, add Using DuoUniversal
  2. Declare string values for Duo Settings
string ClientId = ""; //Setting from Duo
string ClientSecret = ""; //Setting from Duo
string ApiHost = ""; //Setting from Duo
string RedirectUri = "https://localhost:44310/Redirect.aspx"; //This is the URL you will redirect to after authentication. It does not need to be publically accessible.
  1. Create a new method InitiateDuo
public async void InitiateDuo(string User)
	//Create client using config settings
	Client duoClient = new DuoUniversal.ClientBuilder(ClientId, ClientSecret, ApiHost, RedirectUri).Build();
	Session["client"] = duoClient;

	// Check if Duo seems to be healthy and able to service authentications.
	// If Duo were unhealthy, you could possibly send user to an error page, or implement a fail mode
	var isDuoHealthy = await duoClient.DoHealthCheck();

	// Generate a random state value to tie the authentication steps together
	string state = DuoUniversal.Client.GenerateState();
	// Save the state and username in the session for later
	Session["STATE_SESSION_KEY"] = state;
	Session["USERNAME_SESSION_KEY"] = User;

	// Get the URI of the Duo prompt from the client.  This includes an embedded authentication request.
	string promptUri = duoClient.GenerateAuthUri(User, state);

	// Redirect the user's browser to the Duo prompt.
	// The Duo prompt, after authentication, will redirect back to the configured Redirect URI to complete the authentication flow.
	// In this example, that is /duo_callback, which is implemented in Callback.cshtml.cs.

  1. Add the following code to the portion where you want to trigger the Duo prompt.
InitiateDuo(User); //User should be the username trying to login.
  1. On your login.aspx page, add Async=”true” to the top
<%@ Page Async="true" Language="C#"......
  1. Create a new Redirect page which Duo will transfer the user to after authenticating with two factor. Add add Using DuoUniversal to this page.
  2. Create a new method DUOResponse
 public async void DUOResponse(string sessionState, string sessionUsername, string receievedstate, string receivedcode)
	Client duoClient = (Client)Session["client"];

	//Confirm the original state(from the session) matches the state sent by Duo; this helps p
	if (!sessionState.Equals(receievedstate))
		throw new DuoException("Session state did not match the expected state");

	IdToken token = await duoClient.ExchangeAuthorizationCodeFor2faResult(receivedcode, sessionUsername);

	if (token.AuthResult.Result == "allow")
		Session["authenticated"] = true;

  1. In the Page_Load method of the Redirect page add the following code
string sessionState = string.Empty;
if (Session["STATE_SESSION_KEY"] != null)
	sessionState = Session["STATE_SESSION_KEY"].ToString();
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo Session State Key Missing"));

string sessionUsername = string.Empty;
if (Session["userName"] != null)
	sessionUsername = Session["userName"].ToString();
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo Session Username Missing"));

string receievedstate = string.Empty;
if (Request.QueryString["state"] != null)
	receievedstate = Request.QueryString["state"];
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo State URL Variable Missing"));

string receivedcode = string.Empty;
if (Request.QueryString["code"] != null)
	receivedcode = Request.QueryString["code"];
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo Code URL Variable Missing"));

if (string.IsNullOrEmpty(sessionState) || string.IsNullOrEmpty(sessionUsername)
	|| string.IsNullOrEmpty(receievedstate) || string.IsNullOrEmpty(receivedcode))
	Response.Redirect("login.aspx?Msg=" + HttpUtility.UrlEncode("Duo null items"));

DUOResponse(sessionState, sessionUsername, receievedstate, receivedcode);
  1. On your Redirect.aspx page, add Async=”true” to the top
<%@ Page Async="true" Language="C#"......

Build and test. When you test it the first time you will see the old prompt. If everything works, you can go into Duo Admin console and turn on the new prompt.

Duo Universal C# ASP NET Web Forms Read More »

Pool Automation with ESP32 and Arduino

During the COVID pandemic in 2020 we decided to purchase a pool. With my love for technology I wanted a way to fully automate all pool functions through our phones using Home Assistant, HomeKit, Alexa, MQTT and NodeRed. Pool automation is not something the local pool builders are used to in our area and I wasn’t willing to spend big money on one of the big brands; Jandy (iAquaLink), Hayward, Pentair – especially without having knowledge of installing them. I have never worked with microcontrollers before but have had years of experience with programming and integration. This was enough to give me a project through the winter while we were spending our time isolating.

I did my research and purchased a few ESP8266 and ESP32 development boards from Amazon. I ultimately went with the ESP32 due to the increased memory and GPIOs available. Next was to determine which IDE to use and because I have knowledge of c# I felt Arduino would be the easiest to work with. I did get the Visual Micro plugin for Visual Studio as it just streamlined the development process.

ESP8266 –

ESP32 –

I’ve installed remote starters, car stereos, soldering and other various things but I had never worked with electronics to this level. I really need to create a proof of concept to make sure I could actually do this. I also purchased an eight channel relay board from Amazon, miscellaneous jumpers, LED’s, momentary buttons, resistors and diodes.

I was able to prove out the basics of what I would need. Next I wanted to have a touch panel interface and came across Nextion screens. These screens are very easy to use, full support for Arduino and has a GUI builder. I used a Photoshop to mock up some screens and continued the proof of concept.

Now the next challenge was figuring out how to make it all work with my pool equipment. I had various voltage requirements that were necessary to control everything and I wanted a simplified design that looked professional and was reliable. As you can see from the circuit diagram I drew below, I needed:

  • 5V DC for the ESP32 and Nextion screen
  • 12V DC for the momentary LED switches
  • 24V AC for the valve actuators, to control deck jets and waterfall
  • 110V AC to control the pump, pool lights and landscape lights

I headed over to Fiverr and spoke to a few people that specialized in electrical design with microcontrollers and Arduino. One gentlemen I came across, Mithira Udugama, seemed to be newer to Fiverr but we really connected and he seemed genuinely interested and confident in helping me build an electrical circuit and PCB.

After a lot of conversation back and forth understanding the requirements, he built a full electrical schematic, provided a BOM (build of materials), designed the PCB and even helped me print it. Mithira is extremely talented and did such an amazing job that I asked him to put his name on the PCB as well. I HIGHLY recommend him to anyone looking for electrical or PCB design.

Since this was going to be outside I needed to make sure that it could handle the elements and found a NEMA IP67 enclosure with a clean front panel so I could see the touch screen panel and LED buttons.

I started to assemble and wire everything in the enclosure, attempting to keep a clean and professional appearance. I’m happy to report that has been running for a few months now without any issues.

Pool Automation with ESP32 and Arduino Read More »

Home Assistant Integration with HTD MCA-66

Recently I’ve been playing with Home Assistant (Hassio) to have a single pane of all my smart home devices (Zigbee, Z-Wave and IP devices) without having to switch apps to control different devices. One of the major advantages of using home assistant is the plethora of customizations available which can even extend to Google Home and Amazon Alexa. This post will assume you’re already familiar with Home Assistant and focuses on the Home Theater Direct (HTD) integration with the model MCA66. This should work similar with the Lync model by changing the hex codes which is available from HTD.

Most of the code seen here found on different GitHub repositories, but none were specific to my application. They either used Serial communication, or an additional web service and leverages REST api.


For my use case, I only went as far as creating on/off and volume sliders for 4 zones. I use the home assistant Sonos integration for selecting music and sources. The code could be cleaned up as well but haven’t found the time yet.


platform: command_line
 #Zone1 - Master Bedroom
 #Zone2 - Garage
 #Zone3 - 1st Floor
 #Zone4 - 2nd Floor
    command_on: "python3 /config/ pwr 1 1"
    command_off: "python3 /config/ pwr 1 0"
    friendly_name: "Master Bedroom"
    value_template: "{{ value == 'on' }}"
    command_state: "python3 /config/ querypwr 1 0"
    command_on: "python3 /config/ pwr 3 1"
    command_off: "python3 /config/ pwr 3 0" 
    friendly_name: "First Floor"
    value_template: "{{ value == 'on' }}"
    command_state: "python3 /config/ querypwr 3 0"
    command_on: "python3 /config/ pwr 4 1"
    command_off: "python3 /config/ pwr 4 0"
    friendly_name: "Second Floor"
    value_template: "{{ value == 'on' }}"
    command_state: "python3 /config/ querypwr 4 0"
    command_on: "python3 /config/ pwr 2 1"
    command_off: "python3 /config/ pwr 2 0"
    friendly_name: "Garage"
    value_template: "{{ value == 'on' }}"
    command_state: "python3 /config/ querypwr 2 0"
  • If you want to customize the icons to match my screen shot, add the following homeassistant/customize section of configuration.yaml
  icon: mdi:speaker
  assumed_state: false
  icon: mdi:speaker
  assumed_state: false
  icon: mdi:speaker
  assumed_state: false
  icon: mdi:speaker
  assumed_state: false
  • Add the following lines in the homeassistant/packages section of configuration.yaml. This configures the slider bars for volume and limits the volume to 60 (matching HTD).
pack_1: !include /config/packages/
  • Use the Check Config button to confirm syntax, restart your HA server.
  • Create a new lovelace ui card and add the following:
  - entity: switch.htd_firstfloor
  - entity: input_number.slider3
  - entity: switch.htd_masterbedroom
  - entity: input_number.slider1
  - entity: switch.htd_garage
  - entity: input_number.slider2
  - entity: switch.htd_secondfloor
  - entity: input_number.slider4
show_header_toggle: false
title: Speakers
type: entities

This should be all you need. I will update this post if I find time to create source selection, mute, party mode or other functions.

Home Assistant Integration with HTD MCA-66 Read More »

Active Directory Federation Services (ADFS) Authentication Adapter

Microsoft has provided source code to allow a Username and Password to be used as a second factor of authentication in ADFS. You may ask, why would you want to have the usernam and password as a second factor?

  1. Some automated hacking attempts will can lock out your accounts and/or identify a username and password and obtain access through ADFS or another non-authenticated ADFS system. You can setup a different primary authentication like DUO and then username/password seconds.
  2. You may want to allow Windows Integrated Authentication (WIA) for internal connections but require certain relying parties to still require a password for access. Think payroll/financial applications.

The second reason is precisely why I initially was interested, however once it was compiled and deployed to ADFS, clicking the sign in button would not work but hitting the enter key would. After some diagnosing I determined that I was getting Javascript errors on the page referencing InputUtil. I remembered that years ago I had adjusted the onload.js Javascript for the login page of ADFS so that it did not require the domain name within the username text box. This Javascript function was still being called during the second factor authentication and causing the Javascript in the authentication adapter to not run.

To fix this, there are two things that need to be adjusted. Open the file StringResources.resx within the Visual Studio project and go to the key AuthPage. Add the following Javascript below, above function Login(), compile and redeploy to ADFS.

function LoginErrors(){
this.userNameFormatError = 'Enter your user ID in the format 
 or \u0026quot;user@domain\u0026quot;.'; 
this.passwordEmpty = 'Enter your password.'; 
this.passwordTooLong = 'Password must be shorter than 128 characters.';
var maxPasswordLength = 128;

function InputUtil(errTextElementID, errDisplayElementID) {
if (!errTextElementID)  errTextElementID = 'errorText'; 
if (!errDisplayElementID)  errDisplayElementID = 'error'; 

this.hasFocus = false;
this.errLabel = document.getElementById(errTextElementID);
this.errDisplay = document.getElementById(errDisplayElementID);
 InputUtil.prototype.canDisplayError = function () {
     return this.errLabel && this.errDisplay;
 InputUtil.prototype.checkError = function () {
     if (!this.canDisplayError){
         throw new Error ('Error element not present');
     if (this.errLabel && this.errLabel.innerHTML) { = '';        
         var cause = this.errLabel.getAttribute('for');
         if (cause) {
             var causeNode = document.getElementById(cause);
             if (causeNode && causeNode.value) {
                 this.hasFocus = true;
     else { = 'none';
 InputUtil.prototype.setInitialFocus = function (input) {
     if (this.hasFocus) return;
     var node = document.getElementById(input);
     if (node) {
         if ((/^\s*$/).test(node.value)) {
             this.hasFocus = true;
 InputUtil.prototype.setError = function (input, errorMsg) {
     if (!this.canDisplayError) {
         throw new Error('Error element not present');
if (errorMsg) {
    this.errLabel.innerHTML = errorMsg;
this.errLabel.setAttribute('for',; = '';
 InputUtil.makePlaceholder = function (input) {
     var ua = navigator.userAgent;
if (ua != null && 
    (ua.match(/MSIE 9.0/) != null || 
     ua.match(/MSIE 8.0/) != null ||
     ua.match(/MSIE 7.0/) != null)) {
    var node = document.getElementById(input);
    if (node) {
        var placeholder = node.getAttribute("placeholder");
        if (placeholder != null && placeholder != '') {
            var label = document.createElement('input');
            label.type = "text";
            label.value = placeholder;
            label.readOnly = true;
   = 'absolute';
   = 'transparent';
            label.className = node.className + ' hint';
            label.tabIndex = -1;
            label.onfocus = function () { this.nextSibling.focus(); };

   = 'relative';
   = 'relative';
            node.parentNode.insertBefore(label, node);
            node.onkeyup = function () { InputUtil.showHint(this); };
            node.onblur = function () { InputUtil.showHint(this); };
   = 'transparent';

            node.setAttribute("placeholder", "");
 InputUtil.focus = function (inputField) {
     var node = document.getElementById(inputField);
     if (node) node.focus();
 InputUtil.hasClass = function(node, clsName) {
     return node.className.match(new RegExp('(\s|^)' + clsName + '(\s|$)'));
 InputUtil.addClass = function(node, clsName) {
     if (!this.hasClass(node, clsName)) node.className += " " + clsName;
 InputUtil.removeClass = function(node, clsName) {
     if (this.hasClass(node, clsName)) {
         var reg = new RegExp('(\s|^)' + clsName + '(\s|$)');
         node.className = node.className.replace(reg, ' ');
 InputUtil.showHint = function (node, gotFocus) {
     if (node.value && node.value != '') { = 'none';
     else { = '';

You’ll also need to adjust the onload.js Javascript you imported into your ADFS theme. Change function if(Login) with the following.

if(Login) {
     Login.submitLoginRequest = function () { 
    //New Line - This checks to see if the user name text box is visible or not
     if(document.getElementById(Login.userNameInput) != null){
         var u = new InputUtil();
         var e = new LoginErrors();
         var userName = document.getElementById(Login.userNameInput);
         var password = document.getElementById(Login.passwordInput);
         if (userName.value && !userName.value.match('[@\\]')) 
             var userNameValue = 'lorettosystem\' + userName.value;
             document.forms['loginForm'].UserName.value = userNameValue;
  if (!userName.value) {
       u.setError(userName, e.userNameFormatError);
       return false;

    if (!password.value) 
        u.setError(password, e.passwordEmpty);
        return false;
return false;
    return false;

Active Directory Federation Services (ADFS) Authentication Adapter Read More »