GitHub Actions Build with Runner and Deploy using self-hosted

Use GitHub Actions to build and deploy with a private runner to deploy to IIS

Project: ASPX Website on GitHub.

Goal: Complete CI / CD using GitHub Actions to build and using self-hosted running to deploy to on-prem IIS server.

Final Workflow file YML


name: Build and Publish

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  build:
    name: Build
    runs-on: windows-latest
    steps:
        - uses: actions/checkout@v3

        - name: Setup MSBuild path
          uses: microsoft/setup-msbuild@v1.1

        - name: Setup NuGet
          uses: NuGet/setup-nuget@v1.0.5

        - name: Create Build Directory
          run: mkdir ${{github.workspace}}\_build\

        # - name: Show build files (before)
        #   run: ls ${{github.workspace}}\_build\

        - name: Restore NuGet packages
          run: nuget restore -verbosity quiet

        - name: Build app for release
          run: msbuild Website\Website.csproj -verbosity:minimal -t:rebuild -property:Configuration=Release /p:WebPublishMethod=FileSystem /p:DeployOnBuild=true /p:DeployDefaultTarget=WebPublish /p:PublishUrl="../_build"

        # - name: Show build files (after)
        #   run: ls ${{github.workspace}}\_build\

        - uses: actions/upload-artifact@v3
          with:
            name: my-artifact
            path: ${{github.workspace}}\_build\

  deploy:
    needs: Build
    name: Copy WebPublish files to remote server
    runs-on: self-hosted
    steps:

      - name: Run PowerShell Remove-Item 
        run: Remove-Item ${{github.workspace}}\_build\ -Force -Recurse

      - uses: actions/download-artifact@master
        with:
          name: my-artifact
          path: ${{github.workspace}}\_build\

      # - name: Show build files (before)
      #   run: ls ${{github.workspace}}\_build\

      # - name: Run PowerShell Hello World script
      #   run: Write-Output 'Hello World!'

      - name: Run PowerShell Copy-Item
        run: |
            $psversiontable;
            Copy-Item -Path ${{github.workspace}}\_build\* c:/websites/dev.${{github.event.repository.name}} -Recurse -Force

Add WP Bootstrap Navwalker To You WordPress Theme

Why Add Bootstrap Navwalker?

The main reason for adding this addition to your WordPress Theme is best said by its author [Git Link]

A custom WordPress Nav Walker Class to fully implement the Bootstrap 4 navigation style in a custom theme using the WordPress built in menu manager.”

By implementing this code, you will be changing how the menus are generated by WordPress. This will allow the incorporation of Bootstrap 4 styling and standards found on a pure Bootstrap built website. This addition should be pair with an installation of Bootstrap by Twitter [Link] and a consideration for using a Bootstrap focused design.

Please check out “ADDING BOOTSTRAP TO AN UNDERSCORES THEME”  for a detailed guide demonstrating how to add and create a brand new underscores theme with a bootstrap integration.

Downloading the Navwalker Files

Lets head over to the main Git Repo for WP Bootstrap Navwalker [Link]. Go ahead and download a copy of repository and open the downloaded Zip file.

Open the downloaded Zip and select the file called:

class-wp-bootstrap-navwalker.php

Place this file in your active theme folder, or the theme folder you plan to use. File Path should look like so:

Main website folder (folder) -> wp-content (folder) -> themes (folder) -> theme folder (folder) -> add class-wp-bootstrap-navwalker.php

Integration of WP Navwalker

Now that your file has been added to your theme we can start hooking up the Navwalker to your Menus, First lets head over to the functions.php file located in the theme folder – and add the following code:

/**
 * Register Custom Navigation Walker
 */
function register_navwalker(){
	require_once get_template_directory() . '/class-wp-bootstrap-navwalker.php';
}
add_action( 'after_setup_theme', 'register_navwalker' );

If you encounter errors with the above code use a check like this to return clean errors to help diagnose the problem.

if ( ! file_exists( get_template_directory() . '/class-wp-bootstrap-navwalker.php' ) ) {
    // File does not exist... return an error.
    return new WP_Error( 'class-wp-bootstrap-navwalker-missing', __( 'It appears the class-wp-bootstrap-navwalker.php file may be missing.', 'wp-bootstrap-navwalker' ) );
} else {
    // File exists... require it.
    require_once get_template_directory() . '/class-wp-bootstrap-navwalker.php';
}

If no menu has been previously declared, please declare a new menu in your functions.php file.

register_nav_menus( array(
    'primary' => __( 'Primary Menu', 'THEMENAME' ),
) );

Using WP Navwalker in your menu

Lets take a second to look at what we have completed. First, we added the files needed to use WP Navwalker with our theme, added the code necessary to connect the new WP Navwalker file to our WordPress Theme, and for our last step we will create a Menu (most likely located in the header.php file) that will use WP Navwalker!

To update any Wordpress Menu in your theme with the newly added Navwalker use the new wp_nav_menu argument by adding a ‘walker’ item to the wp_nav_menu args array.

wp_nav_menu( array(
    'theme_location'  => 'primary',
    'depth'           => 2, // 1 = no dropdowns, 2 = with dropdowns.
    'container'       => 'div',
    'container_class' => 'collapse navbar-collapse',
    'container_id'    => 'bs-example-navbar-collapse-1',
    'menu_class'      => 'navbar-nav mr-auto',
    'fallback_cb'     => 'WP_Bootstrap_Navwalker::fallback',
    'walker'          => new WP_Bootstrap_Navwalker(),
) );

You menu will now implement the same drop down features found in the Bootstrap by Twitter examples!

Typically additional markup is added to these menus, here is an example of a fixed-top menu that collapse for responsive navigation at the md breakpoint. Additional menu options can also be found in the Bootstrap documentation as well [Link]

<nav class="navbar navbar-expand-md navbar-light bg-light" role="navigation">
  <div class="container">
    <!-- Brand and toggle get grouped for better mobile display -->
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-controls="bs-example-navbar-collapse-1" aria-expanded="false" aria-label="<?php esc_attr_e( 'Toggle navigation', 'your-theme-slug' ); ?>">
        <span class="navbar-toggler-icon"></span>
    </button>
    <a class="navbar-brand" href="#">Navbar</a>
        <?php
        wp_nav_menu( array(
            'theme_location'    => 'primary',
            'depth'             => 2,
            'container'         => 'div',
            'container_class'   => 'collapse navbar-collapse',
            'container_id'      => 'bs-example-navbar-collapse-1',
            'menu_class'        => 'nav navbar-nav',
            'fallback_cb'       => 'WP_Bootstrap_Navwalker::fallback',
            'walker'            => new WP_Bootstrap_Navwalker(),
        ) );
        ?>
    </div>
</nav>

To change your menu style add Bootstrap nav class names to the menu_class declaration.

Review options in the Bootstrap docs for more information on nav classes.

You should now have a fully functioning Bootstrap by Twitter Drop down menu integrating into you WordPress theme!

TLS 1.2 with Classic ASP

Looking at Twillio docs Using Twilio with Classic ASP and VBScript we implanted Twillio SMS API

We encountered the following error message:


Invalid TLS version
Upgrade Requiredhttps://www.twilio.com/docs/errors/20011426

The TLS versions on the server appreared to be causing the error TLS 1.0, TLS 1.1 and TLS 2.0

We used https://www.ssllabs.com/ssltest to get the current TLS Configuration.

The results showed TLS 1.0 and TLS 1.1 where enabled. This was causing the issue as we need TLS 1.0 and TLS 1.1 to be removed.

Using IISCrypto Tool we updated the Registry Files to update the TLS settings

 

 

 

We ran another test using https://www.ssllabs.com/ssltest and the results showed TLS 1.0 and TLS1.1 where no longer available.

 

After we applied these changes and re-booted the server, the Twillio SMS messages where sent successfully.

 

 

However, the SQL Connection String began to break and the Classic ASP website where down.

The SSL Connection was using “SQL Connection” ODBC driver.

We need to update to the latest ODBC to the lastest version which supports TLS 1.2  [Microsoft® ODBC Driver 13.1 for SQL Server]

 

 

 

Then we need to update the connection string to use the current ODBC SQL 13 driver

 

 

Now everything is working!

Classic ASP Function to Set Dynamic Image (1 of 2)

I reviewed some recent client Classic ASP code and found a 4 level deep branching of an IF statement. Reading the code, its intent is to look at the object type and return an icon representing the type.


   <% 
    if  obj.type = "car" then
            response.write("<img src="/images/icons/car.png" />")
    ElseIf obj.type = "boat" then
	    response.write("<img src="/images/icons/boat.png" />")
    ElseIf obj.type = "bike" then
	    response.write("<img src="/images/icons/bike.png" />")
    Else
	    response.write("<img src="/images/icons/placeholder.png" />")
   %>

 

I try to stay away from IF statements altogether but using a SINGLE if statement is something I do use. However, once the IF statement gets past 2 branches, I consider this a CODE SMELL it requires refactoring.

I am going to break my thoughts into small code refactorings so you can understand each step. each step in itself is an improvement to the code, but combined, it will help make changes for the client easier and less prone to bugs.

Step 1

Taking a look at the above code, I notice we are repeating the IMAGE SRC path. So let’s see if we can make better.


   <% 
    Dim imgSrc
    imgSrc = "/images/icons/"

    if  obj.type = "car" then
            response.write("<img src='" & imgSrc & "car.png" />")
    ElseIf obj.type = "boat" then
	    response.write("<img src='" & imgSrc & "boat.png" />")
    ElseIf obj.type = "bike" then
	    response.write("<img src='" & imgSrc & "bike.png" />")
    Else
	    response.write("<img src='" & imgSrc & "placeholder.png" />")
   %>

 

Step 2

Now, we are still repeating the response.write(" block several times. Let’s refactor the IF statement to only have logic about the obj.type.


   <% 
     Dim imgSrc, img
     imgSrc = "/images/icons/"

     img = "placeholder.png"
     if  obj.type = "car" then img = "car.png"
     if  obj.type = "boat" then img = "boat.png"
     if  obj.type = "bike" then img = "bike.png"

     response.write("<img src='" & imgSrc & img & "'" />")


   %>

 

Refactoring Results

The code is much easier to read and to maintain. We can easily add more branches without thinking of the rest of the logic. A non-programmer can look at this code and figure out how to add more images or even change the root path to the images.

In the next post, we will extract the IF statements into a Classic ASP Function.


If you are a looking for Classic ASP Experts or upgrading to .NET from Classic ASP , give us a call.

 

Adding Custom Properties to Objects in Classic ASP

I the last blog post Converting Recordset into Objects in Classic ASP we created a public function initialize(rs) which takes in a single row from the database and creates a single class object account.  Our goal is to add a new property named fullname where we combine firstname and lastname

We will update the public function initialize(rs) as follows:

 

public function initialize(rs)

dim row
set row = New account

   row.id = rs("id")
   row.guid = rs("guid")
   row.lastName = rs("lastName")
   row.firstName = rs("firstName")
   row.fullname = rs("firstName") & ", " & rs("firstName") 
   row.email = rs("email")
   row.password = rs("password")
   row.passwordSalt = rs("passwordSalt")
   row.clientId = rs("clientId")
   row.administrator = rs("administrator")
   row.lastLogin = rs("lastLogin")
   row.created = rs("created")
   row.modified = rs("modified")
   row.active = rs("active")

set initialize = row

end function

Updated the UL code with a new LI item for fullname.

<%
Dim rsAccount
set rsAccount = getall()
%>

<ul>

<%
  While (NOT rsAccount.EOF)

  Dim account
  call account = initialize(rsAccount)

 %>
 
   <li><%=account.id%></li>
   <li><%=account.guid%></li>
   <li><%=account.lastname%></li>
   <li><%=account.firstname%></li>
   <li><%=account.fullname%></li>
   <li><%=account.email%></li>
   <li><%=account.password%></li>
   <li><%=account.passwordSalt%></li>
   <li><%=account.clientId%></li>
   <li><%=account.administrator%></li>
   <li><%=account.lastLogin%></li>
   <li><%=account.created%></li>
   <li><%=account.modified%></li>
   <li><%=account.active%></li>
 
 <% 
  rsAccount.MoveNext()
  Wend
 %>

</ul>

If you run the code as is, you will get an error Variable is undefined: 'fullname'

All we need to do is make sure the account Class a property defined for fullname.

Class account

  public id
  public guid
  public lastName
  public firstName
  public fullname 
  public email
  public password
  public passwordSalt
  public clientId
  public administrator
  public lastLogin
  public created
  public modified
  public active

End Class

Now your code should run without an error. You now have an account object with a property fullname.

If you are a looking for Classic ASP Experts or upgrading to .NET from Classic ASP , give us a call.

 

Converting Recordset into Objects in Classic ASP

Now that you know how to create a Class and Objects in Classic ASP, we will look at Converting Recordset into Objects in Classic ASP.

Lets create a Class account which has several properties. I usually use the database table account and copy the model so the Class matches the database model

Class account

  public id
  public guid
  public lastName
  public firstName
  public email
  public password
  public passwordSalt
  public clientId
  public administrator
  public lastLogin
  public created
  public modified
  public active

End Class

Here I am creating a getall function to return all the rows in the account table in the database.

public function getall()

   dim sql
   sql = "select * from account"

   dim rs
   set rs = conn.execute(sql)
   set getall = rs

end function

When I started with Classic ASP, I would have written a simple While Loop

<%
Dim rsAccount
set rsAccount = getall()
%>

<ul>

<%
  While (NOT rsAccount.EOF)
 %>
 
   <li><%=rsAccount("id")%></li>
   <li><%=rsAccount("guid")%></li>
   <li><%=rsAccount("lastname")%></li>
   <li><%=rsAccount("firstname")%></li>
   <li><%=rsAccount("email")%></li>
   <li><%=rsAccount("password")%></li>
   <li><%=rsAccount("passwordSalt")%></li>
   <li><%=rsAccount("clientId")%></li>
   <li><%=rsAccount("administrator")%></li>
   <li><%=rsAccount("lastLogin")%></li>
   <li><%=rsAccount("created")%></li>
   <li><%=rsAccount("modified")%></li>
   <li><%=rsAccount("active")%></li>
 
 <% 
  rsAccount.MoveNext()
  Wend
 %>

</ul>

The above code would write to the screen a <UL> foreach account row and <LI> foreach field/record in the database.

Object-Oriented Programming Approach

An OOP [Object-Oriented Programming] approach would be to initialize each account record using the account Class.

Instead of getting each data element using Recordset rsAccount("lastName"), we will use another function to map the Recordset row into our account object.

The public function initialize(rs) takes in a single row from the database and creates a single class object account.

 

public function initialize(rs)

dim row
set row = New account

   row.id = rs("id")
   row.guid = rs("guid")
   row.lastname = rs("lastname")
   row.firstname = rs("firstname")
   row.email = rs("email")
   row.password = rs("password")
   row.passwordSalt = rs("passwordSalt")
   row.clientId = rs("clientId")
   row.administrator = rs("administrator")
   row.lastLogin = rs("lastLogin")
   row.created = rs("created")
   row.modified = rs("modified")
   row.active = rs("active")

set initialize = row

end function

Updated code combining Recordset with Class and Initializing each row into an object.

<%
Dim rsAccount
set rsAccount = getall()
%>

<ul>

<%
  While (NOT rsAccount.EOF)

  Dim account
  set account = initialize(rsAccount)

 %>
 
   <li><%=account.id%></li>
   <li><%=account.guid%></li>
   <li><%=account.lastname%></li>
   <li><%=account.firstname%></li>
   <li><%=account.email%></li>
   <li><%=account.password%></li>
   <li><%=account.passwordSalt%></li>
   <li><%=account.clientId%></li>
   <li><%=account.administrator%></li>
   <li><%=account.lastLogin%></li>
   <li><%=account.created%></li>
   <li><%=account.modified%></li>
   <li><%=account.active%></li>
 
 <% 
  rsAccount.MoveNext()
  Wend
 %>

</ul>

If you are a looking for Classic ASP Experts or upgrading to .NET from Classic ASP , give us a call.

 

How to create a Class and Objects in Classic ASP

Creating Objects in programming is one the best ways to have code that is easily maintainable. With new Classic ASP clients, I have found creating Classes and Objects is a very good way to begin refactoring legacy code.

Class trip

    public arrive_date
    public depart_date

End Class

Here we have created a Class trip with 2 properties arrive_date and depart_date. Now, when we need to access the arrival date, you can access the values as

myobject.arrive_date
myobject.depart_date

Before we can access or use the trip Class, we need to create a new instance of a class. The newly instantiated trip Class, which is now an object with the properties, that we can use.

 

Class trip

    public arrive_date
    public depart_date

End Class



dim objTrip
set objTrip = new trip

objTrip.arrive_date = '02/11/2017'
objTrip.depart_date = '02/16/2017'

Now that we have created a new instance of our trip and assigned values to each property, we can write some simple HTML to retrieve the values

<p>

I will be arriving on <%=objTrip .arrive_date%> and leaving on <%=objTrip .depart_date%>

<p>

 

If you are a looking for Classic ASP Experts or upgrading to .NET from Classic ASP , give us a call.

 

Isotope Sorting Ascending / Descending

We worked on a project where we had a media library. We decided to use Isotope to filter/sort through a list of all the media items available. We wanted to the user to be able to sort through the items in both Ascending / Descending order. Below is how we accomplished that.

By default, Isotope sorts ascendingly: A, B, C and 1, 2, 3. To sort descendingly Z, Y, X, and 9, 8, 7, set
sortAscending: false.

// sort highest number first
$grid.isotope({
  sortBy: 'number',
  sortAscending: false
});

We first added a data-attribute called data-sort-direction="asc" on a button element. When the button is clicked we get the data-sort-direction="asc" data-attribute value and convert that value to a boolean. We then use that boolean to set our new direction and store that in a variable called newDirection to use later.

 /* convert it to a boolean */
    var isAscending = (direction == 'asc');
    var newDirection = (isAscending) ? 'desc' : 'asc';

We then pass in our isAscending value into the sortAscending property.

/* pass it to isotope */
    $grid.isotope({ sortBy: sortValue, sortAscending: isAscending });

The last bit of code we use is to change the value data-sort-direction="asc" to the value of newDirection. This is so that next time the button is clicked it will sort the items in the opposite order.

$(this).attr('data-sort-direction', newDirection);

Deleting Custom Fields in WordPress

While working on a project you may have had custom fields that are left behind and that are no longer in use for the current theme these unused custom fields remain in your database. I came across this exact scenario when I was upgrading a section of a website that was outdated and needed to be re-done. The previous programmer had use a plethora of custom fields and my goal was to reduce the amount of custom fields used.

Take a look at the following example. All the custom fields that are not being used

 

Removing Unused Custom Fields

  1. Log into to phpMyAdmin
  2. Select your database.
  3. If you use a different database prefix than the default “wp_” one, then you’ll need to change that in the example below. Replace “meta_key” with your actual custom field name.
  4. Run the following script. ( The script will run and delete any references to that custom field and the data associated with it )
DELETE FROM wp_postmeta WHERE meta_key = 'meta_key';

Alternatively you can also run an sql script that looks at meta_key and see if there are any empty values

SELECT * FROM wp_postmeta WHERE meta_key = 'meta_key';

In my scenario I needed to look for any custom field containing the words productOption running the sql script returned the following.

SELECT * FROM wp_postmeta WHERE meta_key LIKE '%productOption%';

wp-unused-fields-db

Upgrade Wamp Server to PHP 7

We recently upgraded our development and production servers to PHP 7. We develop locally using Wamp Server so we needed to install PHP 7 on our computers as well. After digging around and some trial and errors I found the youtube video below.

Install Microsoft Packages

Download PHP 7

  • Download PHP zip file from http://windows.php.net/download/
  • Choose the correct file 64 or 84 bit Depending on your operating system
  • Choose the Thread Safe file

Create PHP7 Folder in wamp directory

  • Extract contents from PHP7 Download
  • Copy files over to a new folder in wamp\bin\php\php7.0
  • You will need to copy 2 files from an older php version php.ini and wampserver.conf into your new folder wamp\bin\php\php7.0

Modify php.ini

  • In your new folder open php.ini
  • In php.ini find extension_dir and replace the older php version with the new version
  • In php.ini find zend_extension and comment this line out it is no longer needed.
  • Create a copy of php.ini once the above changes are made and rename it to phpForApache.ini

Modify wampserver.conf

  • In your new folder open wampserver.conf and replace LoadModuleName and LoadModuleFile to load the PHP 7 like so

phpConf['apache']['2.4']['LoadModuleName'] = 'php7_module';
$phpConf['apache']['2.4']['LoadModuleFile'] = 'php7apache2_4.dll';
$phpConf['apache']['2.4']['AddModule'] = '';

Start Wamp Load PHP 7

  • Start Wamp > PHP > Version > 7

Notes: PHP 7 needs Apache 2.4 otherwise it will not run. Make Sure Wamp Server is running Apache 2.4. In the video the extensions were changed I tried it and it PHP 7 wouldn’t load in Wamp Server so i kept the same extensions from my previous php.ini file and it worked

Using Google Fonts for web.

Google fonts are a great free resource for web designers. Using them however, wasn’t straight forward for me at first. I’ve created a quick step by step guide for new web designers to use as reference.

Enjoy!


 

How to run a google font on a site.

First, go to https://www.google.com/fonts and select the fonts you wish to use by using the add to collection button.

In the bottom right corner, select use. This will take you to a page asking you to select the font weights you wish to use. Scroll down until you see the lines of code provided.

 

google-font-1

 

Next, open your theme folder from the repository using sublime. You will need to open the functions file and the style.css file.

In the functions file, scroll down to the Enqueue scripts and styles section.

Copy the “add this code to your website” line and paste it into the file with a new wp_enqueue line. It should look like line 163 below.

 

google-font-2

 

Next, open the style.css file and scroll to the typography section. Paste in the “integrate the fonts into your CSS” line as shown below on line 275.

 

google-font-3

 

Save your files and your font should be loaded.

 


 

If you want to use different fonts for headings and body content, make the following adjustments.

 

First, select the two fonts you want to use from https://www.google.com/fonts and select the fonts you wish to use by using the add to collection button, click use, and go to the almost done page.

When you copy the code, copy the line now containing two fonts.

google-font-4

Next, add the body font-family as previously instructed.

For the headings, open the css folder (not the css file used before.) From here, open the styles.css folder.

(For the body font, open the styles.css file highlighted in the picture below. For the header font, open the css folder at the top, then open the styles.css file contained.)
 
google-font-5

 

Once you open the other styles.css file, scroll down to the styles section. Paste in the font-family as shown below.

 

google-font-6

Save and your fonts should now be loaded.