Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Keyboard shortcuts (hotkeys) to move mouse in multi-monitor configuration - AutoHotkey Script

Joe WinogradDeveloper
CERTIFIED EXPERT
50+ years in computers
EE FELLOW 2017 — first ever recipient of Fellow award
MVE 2015,2016,2018
CERTIFIED GOLD EXPERT
DISTINGUISHED EXPERT
Published:
Updated:
Edited by: Rob Jurd
In configurations with multiple monitors, it can be difficult and time-consuming to move the mouse pointer to a particular monitor. This article presents a script written in AutoHotkey that defines hotkeys — such as Alt+Ctrl+1, Alt+Ctrl+2, etc. — to move the mouse easily and quickly to any monitor.
Introduction

In an interesting question here at Experts Exchange, a member requested this:
Code for using Windows AutoHotkey to immediately move mouse pointer to particular monitor

The member goes on to say this:

...several users who have 8 or more monitors connected to their Windows 10 computers

The member indicates that the requirement is to write an AutoHotkey script that defines a series of keyboard shortcuts (aka "hotkeys") to move the mouse pointer to each monitor. In particular:
 
Alt+Ctrl+1 ==> move mouse to monitor 1
Alt+Ctrl+2 ==> move mouse to monitor 2
Alt+Ctrl+3 ==> move mouse to monitor 3
Alt+Ctrl+4 ==> move mouse to monitor 4
Alt+Ctrl+5 ==> move mouse to monitor 5
Alt+Ctrl+6 ==> move mouse to monitor 6
Alt+Ctrl+7 ==> move mouse to monitor 7
Alt+Ctrl+8 ==> move mouse to monitor 8
Alt+Ctrl+9 ==> move mouse to monitor 9
 
I wrote a no-frills script (Version 1) and posted it at the EE thread. After that, however, I decided to write this EE article with an enhanced script. The article began with Version 2 and is now on Version 6. Some screenshots in this article may be from earlier versions. In those cases, the screenshots are identical to the current version, except for the Version and/or Build information.
 
Version 6 Features (Release Notes specific to V6 are at end of article)

• Defines up to 35 hotkeys for 35 monitors. The first nine hotkeys are the ones shown above, i.e., Alt+Ctrl+1 through Alt+Ctrl+9. The next 26 hotkeys utilize the letters of the English alphabet, i.e., Alt+Ctrl+a through Alt+Ctrl+z. Also, defines Alt+Ctrl+0 (zero) as a hotkey to move the mouse to the Primary monitor, regardless of its monitor number/letter.

• The Alt+Ctrl keys are known as modifier keys or, more simply, modifiers. The script allows other modifiers to be used for the hotkeys rather than hard-coding Alt+Ctrl (!^), as follows:

! (exclamation mark) ==> Alt
^ (caret, circumflex, hat) ==> Ctrl
+ (plus sign) ==> Shift
# (hash mark, pound sign, octothorpe) ==> Win (Windows logo key)

You may use any number of those modifiers and they may be in any order, that is, !^ (Alt+Ctrl) behaves the same as ^! (Ctrl+Alt).

• The monitor numbers of 1-9 are established by the script. They may or may not be the same numbers as reported by Windows in Settings>Display. For example, let's say that Settings>Display looks like this (with monitor #1 disconnected):


The script may report them as 2, 1, 3 from left to right (and showing only three monitors because one is disconnected). You can figure out how the script is referring to the monitors via the Identify Monitors feature (discussed below).

The monitor letters of a-z are established by the script and, by their very nature, are different from what Settings>Display shows, since the latter are 10, 11, 12, etc. If CapsLock is on, the upper case letter (A-Z) works as a hotkey. However, using Shift to get the upper case letter does not work as a hotkey, since Shift is recognized as a modifier key.

• Allows horizontal (X) and vertical (Y) offsets to be specified. That is, the mouse pointer will be moved to the specified number of pixels (OffsetX) from the left edge of the monitor and the specified number of pixels (OffsetY) from the top edge of the monitor. If OffsetX is set to -1, it moves the mouse to the horizontal center of the monitor. If OffsetY is set to -1, it moves the mouse to the vertical center of the monitor.

• Displays an icon in the system tray, which is included in the attached ZIP file. The icon is a combination of multiple monitors and a mouse. It looks like:


• Displays a tooltip when hovering on the system tray icon. The tooltip shows the file name of the script (without its path and file extension) and brief usage instructions. It looks like this:


• Displays this context menu when right-clicking on the system tray icon:


• Selecting the Show Monitor and Virtual Screen Information menu item displays a dialog that looks like this:


The monitor numbers/letters, known as the monitor identifiers (1, 2, and 3 in the example above, followed by a colon) are established by the script, as noted earlier (they may or may not be the same identifiers as in Settings>Display). The monitor names (such as \\.\DISPLAY8 in the example above) are the names reported by Windows. Those names overall, and the ending numbers specifically (such as 8, 9, and 10 in the example above) are assigned by Windows.

The L, R, T, and B values next to each monitor name are the bounding monitor coordinates for that monitor — Left, Right, Top, and Bottom.

The VirtualX and VirtualY values are the coordinates for the left edge and top edge of the virtual screen, respectively. The VirtualW and VirtualH values are the width and height of the virtual screen, respectively. The virtual screen is the bounding rectangle of all the monitors.

• Selecting the Identify Monitors menu item displays the monitor identifier (number or letter) on each monitor for however many seconds you prefer (set at three seconds in the attached script — change it to whatever you want). The monitor identifier is 1 through 9 or a through z. The numbers are on a blue background and look like this:


The letters are on a red background and look like this (all are displayed in upper case):


Those identifiers are the ones established by the script and used by the hotkeys, such as  Alt+Ctrl+3 and Alt+Ctrl+e. They are also the identifiers reported in the Show Monitor and Virtual Screen Information dialog. The top image in this article shows the nine identifying numbers in that nine-monitor configuration.

The image files (PNG) for all nine numbers and all 26 letters are included in the attached ZIP file.

• Selecting the Start with Windows (On/Off toggle) menu item toggles that setting, i.e., if it is Off (the default), it turns On; if it is On, it turns Off. When it is On, a checkmark appears next to it in the context menu:


The method used to start the script with Windows is to create a shortcut (a .LNK file) and put it in the Startup folder (the one for the logged in user, not the Public one). The icon for it is the same one that appears in the system tray (shown previously).

Turning this off deletes the shortcut in the Startup folder; turning it on creates the shortcut. This setting is "sticky" (survives exiting the script).

• Selecting the Reload Script menu item exits the script and immediately re-runs it.

• Selecting the About menu item displays a dialog box with a Title bar that contains the file name of the script without its path and file extension. The body of the dialog contains three lines:

(1) The full path/name/extension of the script.

(2) The Version of the script as specified in the assignment statement on line 2 of the source code.
 
(3) The Modified date/time stamp of the script file.
 
Here's an example of it:


• Selecting the Exit menu item exits (terminates/quits) the script, thereby removing the icon from the system tray and un-defining all the hotkeys. To make sure that this wasn't selected by accident, it displays this dialog, with No as the default button (so that an accidental Enter key won't cause an unwanted exit):


The Script — Source Code
 
As noted at the top of this article, the Experts Exchange member specifically asked for an AutoHotkey solution. If you are not familiar with the language, my EE article will get you going on it:

AutoHotkey - Getting Started

The script is in a code block below. However, it is important to note that it requires supporting files. In other words, you cannot simply copy/paste the code block below into an AHK source code file and run it — you will be missing required files (specifically, ICO and PNG files). To execute the script properly, download the ZIP file attached at the end of the article and unpack all the files into the same folder. It contains the same AutoHotkey source code shown in the code block below along with all the supporting files required for the script to work.

; Joe Winograd 21-Aug-2021
Version:="6"
#Warn,UseUnsetLocal ; warning on uninitialized variables
#NoEnv ; avoid checking empty variables to see if they are environment variables
#SingleInstance Force ; replace old instance immediately
SetBatchLines,-1 ; run at maximum speed

Gosub,InitializeVars ; initialize all variables
Gosub,ConfigureInitialTray ; configure initial system tray (notification area)
Return

InitializeVars:
SplitPath,A_ScriptName,,,,ProgramName ; ProgramName==>script name without path or extension
FileGetTime,ScriptDateTime,%A_ScriptName%,M ; modified time of script
FormatTime,ScriptDateTime,%ScriptDateTime%,yyyyMMdd-HHmmss
; icon file name is now hard-coded so that script can have any name without having to change the icon file name (had been based on ProgramName in previous versions)
IconFile:=A_ScriptDir . "\MoveMouseToMonitor.ico" ; credit to the Oxygen team at https://iconarchive.com/show/oxygen-icons-by-oxygen-icons.org.html for the monitor and mouse icons

; *** begin variables to change ***
IdentifySeconds:=3 ; length of time in seconds for monitor identifier (its number or letter) to stay on screen
OffsetX:=-1 ; move mouse to this number of pixels from left edge of monitor (-1 means center)
OffsetY:=-1 ; move mouse to this number of pixels from top edge of monitor (-1 means center)
;OffsetX:=0 ; example - left edge
;OffsetY:=0 ; example - top edge
;OffsetX:=400 ; example - 400 pixels from left edge
;OffsetY:=300 ; example - 300 pixels from top edge
; modifier keys:  ! is Alt   ^ is Ctrl   + is Shift   # is Win (the Windows logo key)
HotkeyModifiers:="!^" ; modifier keys for identifier keys (0-9 and a-z): ! is Alt, ^ is Ctrl, + is Shift, # is Win ((Windows logo key) - modifiers may be in any order
; *** end variables to change ***

SplashOffset:=128 ; size of Identify Monitor PNGs divided by 2 so that this splash offset centers the images on the  screen (the PNGs are 256x256)
; need to be careful in code when to use the Windows monitor number, which is always a number, and the script monitor identifier, which is a number or letter
; these two arrays make it easy to switch between the Windows monitor number and the script monitor identifier
; this is a simple array - the index is the monitor number, the value is the monitor identifier
MonitorIDs:=["1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
; this is an associative array - the key:value pair is monitor identifier:monitor number
MonitorNums:={1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",a:"10",b:"11",c:"12",d:13,e:"14",f:"15",g:"16",h:"17",i:"18",j:"19",k:"20",l:"21",m:"22",n:"23",o:"24",p:"25",q:"26",r:"27",s:"28",t:"29",u:"30",v:"31",w:"32",x:"33",y:"34",z:"35"} ; associative array with key:value pairs for getting (Windows) monitor number from (script) monitor identifier

IdentifyMilliseconds:=IdentifySeconds*1000 ; Sleep time is in milliseconds
NumModifiers:=StrLen(HotkeyModifiers) ; get number of modifier keys
Hotkey,%HotkeyModifiers%0,MoveMousePrimary,On ; define Modifiers+0 hotkey to move mouse to Primary monitor regardless of its monitor identifier
SysGet,OrigNumMons,MonitorCount ; original number of monitors when script was run
Loop,%OrigNumMons% ; process all monitors
{
  MonitorHotkey:=HotkeyModifiers . MonitorIDs[A_Index]
  Hotkey,%MonitorHotkey%,MoveMouseMon,On ; define Modifiers+ID hotkey
}
Return

ConfigureInitialTray:
Menu,Tray,NoStandard ; do not use standard AutoHotkey context menu
Menu,Tray,Add,Show &Monitor and Virtual Screen Information,ContextMenu
Menu,Tray,Add,&Identify Monitors,ContextMenu
Menu,Tray,Add,Start with &Windows (On/Off toggle),ContextMenu
StartupLink:=A_Startup . "\" . ProgramName . ".lnk"
If (FileExist(StartupLink))
  Menu,Tray,Check,Start with &Windows (On/Off toggle)
Else
  Menu,Tray,Uncheck,Start with &Windows (On/Off toggle)
Menu,Tray,Add,&Reload Script,ContextMenu
Menu,Tray,Add,&About,ContextMenu
Menu,Tray,Add,E&xit,ContextMenu
Menu,Tray,Default,Show &Monitor and Virtual Screen Information
HotkeyModifiersTip:=StrReplace(HotkeyModifiers,"+","Shift+") ; do this first so we don't catch the other plus signs
HotkeyModifiersTip:=StrReplace(HotkeyModifiersTip,"!","Alt+")
HotkeyModifiersTip:=StrReplace(HotkeyModifiersTip,"^","Ctrl+")
HotkeyModifiersTip:=StrReplace(HotkeyModifiersTip,"#","Win+")

TrayTip:=ProgramName . "`n" . HotkeyModifiersTip . "Number 0-9 or Letter a-z (zero always primary)`nRight-click for context menu"
Menu,Tray,Tip,%TrayTip%
Menu,Tray,Icon,%IconFile%
Return

MoveMouseMon:
MonID:=SubStr(A_ThisHotkey,NumModifiers+1,1) ; monitor identifier is after modifiers
MonNum:=MonitorNums[MonID] ; get monitor number from monitor identifier
PerformMove(MonNum,OffsetX,OffsetY) ; move it
Return

MoveMousePrimary:
SysGet,PrimaryMonNum,MonitorPrimary ; get number of Primary monitor (the Windows number, not the script identifier)
PerformMove(PrimaryMonNum,OffsetX,OffsetY) ; move it
Return

PerformMove(MoveMonNum,OffX,OffY)
{
  global MoveX,MoveY
  Gosub,CheckNumMonsChanged ; before performing move, check if the number of monitors has changed
  RestoreDPI:=DllCall("SetThreadDpiAwarenessContext","ptr",-3,"ptr") ; enable per-monitor DPI awareness and save current value to restore it when done - thanks to lexikos for this
  SysGet,Coordinates%MoveMonNum%,Monitor,%MoveMonNum% ; get coordinates for this monitor
  Left:=Coordinates%MoveMonNum%Left
  Right:=Coordinates%MoveMonNum%Right
  Top:=Coordinates%MoveMonNum%Top
  Bottom:=Coordinates%MoveMonNum%Bottom
  If (OffX=-1)
    MoveX:=Left+(Floor(0.5*(Right-Left))) ; center
  Else
    MoveX:=Left+OffX
  If (OffY=-1)
    MoveY:=Top+(Floor(0.5*(Bottom-Top))) ; center
  Else
    MoveY:=Top+OffY
  DllCall("SetCursorPos","int",MoveX,"int",MoveY) ; first call to move it - usually works but not always
  Sleep,10 ; wait a few milliseconds for first call to settle
  DllCall("SetCursorPos","int",MoveX,"int",MoveY) ; second call sometimes needed
  DllCall("SetThreadDpiAwarenessContext","ptr",RestoreDPI,"ptr") ; restore previous DPI awareness - thanks to lexikos for this
  Return
}

CheckNumMonsChanged:
SysGet,CurrNumMons,MonitorCount ; current number of monitors
If (OrigNumMons!=CurrNumMons)
{
  MsgBox,4144,Warning,Number of monitors changed since script was run`nOriginal=%OrigNumMons%`nCurrent=%CurrNumMons%`n`nWill reload script when you click OK button
  ; since the number of monitors has changed, disable all hotkeys - the reload will enable the new/correct ones
  Loop,%OrigNumMons% ; process all current monitors
  {
    MonitorHotkey:=HotkeyModifiers . MonitorIDs[A_Index]
    Hotkey,%MonitorHotkey%,,Off ; disable hotkey
  }
  Reload
  Sleep,2000 ; give Reload two seconds to work during this Sleep - if Reload successful, will never get to code below
  MsgBox,4112,Error,Unable to reload script`nWill exit when you click OK button`nYou will have to re-run the script manually
  ExitApp
}
Return ; number of monitors has not changed

ContextMenu:
If (A_ThisMenuItem="Show &Monitor and Virtual Screen Information")
{
  Gosub,CheckNumMonsChanged ; before showing info, check if number of monitors has changed
  RestoreDPI:=DllCall("SetThreadDpiAwarenessContext","ptr",-3,"ptr") ; enable per-monitor DPI awareness and save current value to restore it when done - thanks to lexikos for this
  AllMons:=""
  Loop,%OrigNumMons% ; process all monitors
  {
    MonNum:=A_Index
    SysGet,MonName,MonitorName,%MonNum% ; get name of this monitor
    SysGet,Coordinates%MonNum%,Monitor,%MonNum% ; get coordinates for this monitor
    Left:=Coordinates%MonNum%Left
    Right:=Coordinates%MonNum%Right
    Top:=Coordinates%MonNum%Top
    Bottom:=Coordinates%MonNum%Bottom
    AllMons:=AllMons . MonitorIDs[A_Index] . ": " . MonName . " L=" . Left . " R=" . Right . " T=" . Top . " B=" . Bottom . "`n"
  }
  SysGet,VirtualX,76 ; coordinate for left side of virtual screen
  SysGet,VirtualY,77 ; coordinate for top of virtual screen
  SysGet,VirtualW,78 ; width of virtual screen
  SysGet,VirtualH,79 ; height of virtual screen
  SysGet,PrimaryMonNum,MonitorPrimary ; get number of Primary monitor (the Windows number, not the script identifier)
  MsgBox,4160,%ProgramName%,Monitor numbers/letters`, names`, coordinates:`n%AllMons%Primary monitor number/letter: %PrimaryMonNum%`n`nVirtual screen information:`nVirtualX=%VirtualX%`nVirtualY=%VirtualY%`nVirtualW=%VirtualW%`nVirtualH=%VirtualH%
  DllCall("SetThreadDpiAwarenessContext","ptr",RestoreDPI,"ptr") ; restore previous DPI awareness - thanks to lexikos for this
  Return
}

If (A_ThisMenuItem="&Identify Monitors")
{
  Gosub,CheckNumMonsChanged ; before identifying, check if the number of monitors has changed
  RestoreDPI:=DllCall("SetThreadDpiAwarenessContext","ptr",-3,"ptr") ; enable per-monitor DPI awareness and save current value to restore it when done - thanks to lexikos for this
  VarSetCapacity(PosPtr,4,0) ; get current position so it can be restored after identify
  DllCall("GetCursorPos","ptr",&PosPtr)
  SaveX:=NumGet(PosPtr,0,"int")
  SaveY:=NumGet(PosPtr,4,"int")
  SysGet,NumMons,MonitorCount
  Loop,%NumMons% ; process all monitors
  {
    MonID:=MonitorIDs[A_Index] ; get identifier
    PerformMove(MonID,-1,-1) ;  put identifier in center of screen
    IdentifyImage:=A_ScriptDir . "\Monitor" . MonID . ".png" ; credit to https://iconarchive.com/show/red-orb-alphabet-icons-by-iconarchive.html for the images
    SplashX:=MoveX-SplashOffset ; center splash image horizontally
    SplashY:=MoveY-SplashOffset ; center splash image vertically
    SplashImage,%MonID%:%IdentifyImage%,B X%SplashX% Y%SplashY%
  }
  Sleep,%IdentifyMilliseconds%
  Loop,%NumMons% ; process all monitors
  {
    MonID:=MonitorIDs[A_Index]
    SplashImage,%MonID%:Off
  }
  DllCall("SetCursorPos","int",SaveX,"int",SaveY)
  DllCall("SetThreadDpiAwarenessContext","ptr",RestoreDPI,"ptr") ; restore previous DPI awareness - thanks to lexikos for this
  Return
}

If (A_ThisMenuItem="Start with &Windows (On/Off toggle)")
{
  If (FileExist(StartupLink))
  {
    ; it's on, so this click turns it off
    Menu,Tray,Uncheck,Start with &Windows (On/Off toggle)
    FileDelete,%StartupLink%
    Return
  }
  Else
  {
    ; it's off, so this click turns it on
    Menu,Tray,Check,Start with &Windows (On/Off toggle)
    FileCreateShortcut,%A_ScriptFullPath%,%StartupLink%,%A_ScriptDir%,,%ProgramName%,%IconFile%
    {
      If (ErrorLevel!=0)
      {
        MsgBox,4112,Fatal Error,Error Level=%ErrorLevel% trying to create Startup shortcut:`n%StartupLink%
        ExitApp
      }
    }
    Return
  }
}

If (A_ThisMenuItem="&Reload Script")
{
  Reload
  Sleep,2000 ; give Reload two seconds to work during this Sleep - if Reload successful, will never get to code below
  MsgBox,4112,Error,Unable to reload script`nWill exit when you click OK button`nYou will have to reload the script manually
  ExitApp
}

If (A_ThisMenuItem="&About")
{
  MsgBox,4160,About %ProgramName%,%A_ScriptFullPath%`n`nVersion %Version%`n`nModified: %ScriptDateTime%
  Return
}

If (A_ThisMenuItem="E&xit")
{
  MsgBox,4388,%ProgramName% - Terminate?,Are you sure you want to quit and deactivate all hotkeys?
  IfMsgBox,No
    Return
  ExitApp
}

The script should work in all versions of Windows from XP through W11, both 32-bit and 64-bit, with up to 35 monitors. I tested it on XP/32-bit with two monitors, W7/64-bit with three monitors, W10/64-bit with two monitors, W10/64-bit with three monitors, and W11/64-bit with two monitors (the latest Windows 11 Insider Preview at the time of updating this article: Version 21H2, Build 22000.120) — all worked perfectly. If you use it on other configurations — a different version of Windows and/or a different number of monitors — I'll really appreciate it if you post your results as a comment under the article so that I can add it to the section below.

Results from Readers

• After I fixed the DPI scaling bug, Richard Chu reported success in W10 with a four-monitor configuration. You may see all his feedback in the comments under this article beginning on 2-Oct-2020.

Uday Sawhney reported success on 5-Nov-2020 in W10 with a five-monitor configuration. I was gratified to see him say, "Great program. I have PAID for programs that do not simply work. Your [program] does." All his feedback is at this EE Q&A thread.

• On 7-Sep-2021, a reader who posts as Maysun said, "I am sooo happy. It works!! I have filled in the hotkeys for two of the buttons on my Huion Inspiroy pen tablet."

Acknowledgements

My thanks to Andrew Leniart, Senior Editor here at Experts Exchange, for his deep knowledge of the article editor and his ability to solve formatting problems. He has spent extensive time and effort on improving the appearance of this article (and many others!), for which I'm extremely grateful.

My thanks to Richard Chu for reporting the DPI scaling bug and running many tests to help me troubleshoot the problem and solve it.

My thanks to Steve Gray, whose online identity is Lexikos, for the per-monitor DPI awareness code.

Release Notes for Version 6

• Changed positioning of Identify Monitors splash images. Previously, the upper left corner of the image was in the center of the monitor. Now the center of the image is in the center of the monitor.

• Changed initial value for Identify Monitors splash images to stay on the screen to three seconds. Previously, it was five seconds (of course, change it to whatever you prefer).

• Changed file name of the system tray icon file. Previously, it was based on the file name of the script. Now it is hard-coded as MoveMouseToMonitor.ico so that the script can have any file name without needing to change the icon file name.

• Improved comments in the code.

• Fixed bug when number of monitors is 10 or more.

Conclusion

My hope is that the descriptive variable names along with the extensive comments in the script provide enough documentation for readers to modify it, but if you have any questions, post them here, and I'll try to assist.

If you find this article to be helpful, please click the thumbs-up icon below. This lets me know what is valuable for EE members and provides direction for future articles. Thanks very much! Regards, Joe

MoveMouseToMonitorV6.zip



6
23,806 Views
Joe WinogradDeveloper
CERTIFIED EXPERT
50+ years in computers
EE FELLOW 2017 — first ever recipient of Fellow award
MVE 2015,2016,2018
CERTIFIED GOLD EXPERT
DISTINGUISHED EXPERT

Comments (38)

Joe WinogradDeveloper
CERTIFIED EXPERT
Fellow
Most Valuable Expert 2018

Author

Commented:
You're welcome, Richard, and thanks to you for doing some digging, too. Regards, Joe
Joe WinogradDeveloper
CERTIFIED EXPERT
Fellow
Most Valuable Expert 2018

Author

Commented:
Hi Richard,
A quick note to let you know that I haven't found a work-around with AutoHotkey. If I happen to come across one, I'll let you know, of course, but at this point I'm not going to be actively researching it. Regards, Joe
Hi Joe,
Much appreciated for it! I have also been looking for it, but seems most forum said its not possible at the moment. I have also added this feature request to the PowerToys, hopefully someday they can implement it.

https://github.com/microsoft/PowerToys/preleases/

Thank you once again Joe! Much appreciated!
Cheers
Richard
Joe WinogradDeveloper
CERTIFIED EXPERT
Fellow
Most Valuable Expert 2018

Author

Commented:
Hi Richard,
Great idea to make it a feature request for PowerToys! Please let me know if you get any feedback on it. Thanks, Joe
Absolutely will do! Thank you Joe!

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.