Brandon

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. https://www.homedepot.com/p/Coolaroo-Stone-Cordless-UV-Blocking-Fade-Resistant-Fabric-Exterior-Roller-Shade-96-in-W-x-96-in-L-471309/315637152 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 https://amzn.to/3lxhaFp 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. https://low.es/3sRQ148. 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. https://thd.co/3yYEc01. 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.

cover:
  - platform: template
    covers:
      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
        open_cover:
          service: remote.send_command #service to leverage to raise the shade up
          data:
            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  
          target:
            entity_id: remote.broadlink_pro_remote #entity name of BroadLink RM4 Pro
        close_cover:
          service: remote.send_command  #service to leverage to lowerthe shade down
          data:
            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
          target:
            entity_id: remote.broadlink_pro_remote #entity name of BroadLink RM4 Pro
        stop_cover:
          service: remote.send_command  #service to leverage to stop the shade
          data:
            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
          target:
            entity_id: remote.broadlink_pro_remote #entity name of BroadLink RM4 Pro
      #####If you have a second shade#######
      pool_pavilion_shade_side: 
        friendly_name: 
        unique_id: "PavillionSideShade"
        device_class: shade
        position_template: 50
        open_cover:
          service: remote.send_command
          data:
            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  
          target:
            entity_id: remote.broadlink_pro_remote
        close_cover:
          service: remote.send_command
          data:
            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
          target:
            entity_id: remote.broadlink_pro_remote
        stop_cover:
          service: remote.send_command
          data:
            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
          target:
            entity_id: remote.broadlink_pro_remote
  

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]
AND
Subject Contains: Alert
EXCEPT
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.

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!

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'

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 https://github.com/duosecurity/duo_universal_csharp 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.
	Response.Redirect(promptUri,false);

}
  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 ProcessDuoResponse
public async void ProcessDuoResponse()
{
	//Can use same client or create a new one
	Client duoClient = (Client)Session["client"];

	//Get Session information from login page
	string sessionState = (string)Session["STATE_SESSION_KEY"];
	string sessionUsername = (string)Session["USERNAME_SESSION_KEY"];

	string receievedstate = Request.QueryString["state"];
	string receivedcode = Request.QueryString["code"];
	// If either is missing, something is wrong.
	if (string.IsNullOrEmpty(sessionState) || string.IsNullOrEmpty(sessionUsername))
	{
		Response.Redirect("Login.aspx");
		throw new DuoException("State or username were missing from your session");
	}

	//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");
	}
	Session.Clear();

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

	if (token.AuthResult.Result == "allow")
		Response.Redirect("LoggedIn.aspx"); //Redirect to the final logged in page

}
  1. In the Page_Load method add the following code
string sessionState = (string)Session["STATE_SESSION_KEY"];
string sessionUsername = (string)Session["USERNAME_SESSION_KEY"];
string receievedstate = Request.QueryString["state"];
string receivedcode = Request.QueryString["code"];

// If either is missing, something is wrong.
if (string.IsNullOrEmpty(sessionState) || string.IsNullOrEmpty(sessionUsername)
    || string.IsNullOrEmpty(receievedstate) || string.IsNullOrEmpty(receivedcode))
{
      //Redirect to login page and throw appropriate error message.
      Response.Redirect("Login.aspx");
      return;
}

        
  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.

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 – https://www.amazon.com/dp/B081CSJV2V/ref=cm_sw_em_r_mt_dp_8VXX29SDKAKVQHX2BZG7?_encoding=UTF8&psc=1

ESP32 – https://www.amazon.com/gp/product/B086MLNH7N/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1

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.
https://www.fiverr.com/mithiraudugama/design-and-develop-electronic-circuits-for-your-requirements

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.

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.

  • https://github.com/whitingj/mca66
  • https://github.com/steve28/mca66

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.

Instructions

platform: command_line
 switches:
 #Zone1 - Master Bedroom
 #Zone2 - Garage
 #Zone3 - 1st Floor
 #Zone4 - 2nd Floor
   htd_masterbedroom: 
    command_on: "python3 /config/htd.py pwr 1 1"
    command_off: "python3 /config/htd.py pwr 1 0"
    friendly_name: "Master Bedroom"
    value_template: "{{ value == 'on' }}"
    command_state: "python3 /config/htdquery.py querypwr 1 0"
   htd_firstfloor: 
    command_on: "python3 /config/htd.py pwr 3 1"
    command_off: "python3 /config/htd.py pwr 3 0" 
    friendly_name: "First Floor"
    value_template: "{{ value == 'on' }}"
    command_state: "python3 /config/htdquery.py querypwr 3 0"
   htd_secondfloor:
    command_on: "python3 /config/htd.py pwr 4 1"
    command_off: "python3 /config/htd.py pwr 4 0"
    friendly_name: "Second Floor"
    value_template: "{{ value == 'on' }}"
    command_state: "python3 /config/htdquery.py querypwr 4 0"
   htd_garage:
    command_on: "python3 /config/htd.py pwr 2 1"
    command_off: "python3 /config/htd.py pwr 2 0"
    friendly_name: "Garage"
    value_template: "{{ value == 'on' }}"
    command_state: "python3 /config/htdquery.py querypwr 2 0"
  • If you want to customize the icons to match my screen shot, add the following homeassistant/customize section of configuration.yaml
switch.htd_garage:
  icon: mdi:speaker
  assumed_state: false
switch.htd_masterbedroom:
  icon: mdi:speaker
  assumed_state: false
switch.htd_secondfloor:
  icon: mdi:speaker
  assumed_state: false
switch.htd_firstfloor:
  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/mca662.py
  • Use the Check Config button to confirm syntax, restart your HA server.
  • Create a new lovelace ui card and add the following:
entities:
  - 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.

Active Directory Federation Services (ADFS) Authentication Adapter

https://github.com/Microsoft/adfsAuthAdapters/tree/master/UsernamePasswordSecondFactor

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. https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/operations/advanced-customization-of-ad-fs-sign-in-pages. 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 
\u0026quot;domain\user\u0026quot;
 or \u0026quot;[email protected]\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) {
         this.errDisplay.style.display = '';        
         var cause = this.errLabel.getAttribute('for');
         if (cause) {
             var causeNode = document.getElementById(cause);
             if (causeNode && causeNode.value) {
                 causeNode.focus();
                 this.hasFocus = true;
             }
         }
     }
     else {
         this.errDisplay.style.display = 'none';
     }
};
 InputUtil.prototype.setInitialFocus = function (input) {
     if (this.hasFocus) return;
     var node = document.getElementById(input);
     if (node) {
         if ((/^\s*$/).test(node.value)) {
             node.focus();
             this.hasFocus = true;
         }
     }
 };
 InputUtil.prototype.setError = function (input, errorMsg) {
     if (!this.canDisplayError) {
         throw new Error('Error element not present');
     }
     input.focus();
if (errorMsg) {
    this.errLabel.innerHTML = errorMsg;
}
this.errLabel.setAttribute('for', input.id);
this.errDisplay.style.display = '';
};
 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;
            label.style.position = 'absolute';
            label.style.borderColor = 'transparent';
            label.className = node.className + ' hint';
            label.tabIndex = -1;
            label.onfocus = function () { this.nextSibling.focus(); };

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

            node.setAttribute("placeholder", "");
            InputUtil.showHint(node);
        }
    }
}
};
 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 != '') {
         node.previousSibling.style.display = 'none';
     }
     else {
         node.previousSibling.style.display = '';
     }
 };

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;
    }
      document.forms['loginForm'].submit();
return false;
}else{
    document.forms['loginForm'].submit();
    return false;
}
};
 }


Popup Intercept – One Window

Released my first publically available Google Chrome Extension called Popup Intercept – One Window!

https://chrome.google.com/webstore/detail/popup-intercept-one-windo/fpkgadnkojegbmhdflhhcjkhgjijiakm

This extension allows you to customize which addresses will open up into a tab of the main Google Chrome window via the options menu. I know there are some other extensions out there that do something similar but none of them offered a way to customize it by URL.

 

Why did you make this?

This started with creating an extension that I could click a button and perform an action, however that action needed to be performed on a popup window. Unfortunately, popup windows don’t have a menu or extensions available to them.

Removing locally installed printers and replacing with print server

Recently ran into an environment where there was a print server installed and managing all 200 printers however some users (380 computers) still had printers installed locally and were printing directly to the printer through the IP address or host name. This can cause issues with permissions, driver updates, or logging.

Instead of manually touching each computer I created a PowerShell script to verify it was a network printer installed locally and if it existed on the print server to install it for the user, reducing phone calls to the help desk. The script also confirms they are on the corporate network to continue and run and also verifies the printer is online so that it doesn’t remove personal printers that may have been installed for home use.

 
   1: #Only run if on the corporate network to prevent removing personal printers

   2: $Continue = $false

   3:  

   4: $DNSAddresses = Get-DNSClientServerAddress | Select-Object -ExpandProperty ServerAddresses

   5:  

   6: foreach ($DNSAddresses in $DNSAddresses)

   7: {

   8:     #Check DNS server IP to ensure on corporate network

   9:     if($DNSAddresses -eq "192.168.x.x")

  10:     {

  11:         $Continue = $true

  12:         Write-Host "We are on corporate network, continuing.."

  13:         break

  14:     }

  15: }

  16:  

  17: if($Continue -eq $true)

  18: {

  19:     $LocalPrinters = Get-Printer

  20:  

  21:     foreach ($printer in $LocalPrinters)

  22:     {

  23:         if($printer.Type -eq 'Local')

  24:         {

  25:  

  26:             $Port = $printer.PortName.Split("_")

  27:        

  28:             #Checking if a live printer on corporate network

  29:             $ConnectionResults = Test-Connection $Port[0] -Count 1 -Quiet

  30:  

  31:             #Checking if printer is a network printer

  32:             if($ConnectionResults -eq $true)

  33:             {

  34:                 Write-Host "Deleting printer: " $printer.Name

  35:                 Remove-Printer -Name $printer.Name

  36:  

  37:                 #Checking if printer is on print server so we can re-add it

  38:                  $PrintServerPrinterPortName = Get-Printer -ComputerName "printserver.domain.org" -Name $Port[0]

  39:  

  40:                 Write-Host "Printers found on print server with same port name: "$PrintServerPrinterPortName.Count

  41:  

  42:                 if($PrintServerPrinterPortName.Count -gt 0)

  43:                 {

  44:                     $AddPrinter = "\\printserver.domain.org\" + $Port[0]

  45:                    Write-Host "Adding printer from print server: " + $AddPrinter

  46:                   

  47:                    Add-Printer -ConnectionName $AddPrinter

  48:                 }

  49:                 else

  50:                 {

  51:                     Write-Host "Did not find printer on print server with same port name. Checking printer name."

  52:                     $PrintServerPrinterName = Get-Printer -ComputerName "printserver.domain.org" -Name $printer.Name

  53:  

  54:                     Write-Host Write-Host "Printers found on print server with same printer name: "$PrintServerPrinterName.Count

  55:  

  56:                     if($PrintServerPrinterName.Count -gt 0)

  57:                     {

  58:                         $AddPrinter = "\\printserver.domain.org\" +$printer.Name

  59:                         Write-Host "Adding printer from print server: " + $AddPrinter

  60:                         

  61:                         Add-Printer -ConnectionName $AddPrinter

  62:                     }

  63:                     else

  64:                     {

  65:                         Write-Host "Exhausted all options and no printers found."

  66:                     }                

  67:                 }

  68:             }        

  69:         }

  70:     }

  71: }

  72: