raw.githubusercontent.com Open in urlscan Pro
2606:50c0:8003::154  Public Scan

Submitted URL: https://bit.ly/LTPoSh
Effective URL: https://raw.githubusercontent.com/LabtechConsulting/LabTech-Powershell-Module/master/LabTech.psm1
Submission: On October 28 via manual from CA — Scanned from CA

Form analysis 0 forms found in the DOM

Text Content

<#
.SYNOPSIS
    This is a PowerShell Module for LabTech.
    labtechconsulting.com
    labtechsoftware.com
    msdn.microsoft.com/powershell


.DESCRIPTION
    This is a set of commandlets to interface with the LabTech Agent.
    Tested Versions: v10.5-v12, v2019-v2024

.NOTES
    Version:        1.9.2
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 1/23/2018
    Purpose/Change: Updates to address 32-bit vs. 64-bit environments

    Update Date: 2/1/2018
    Purpose/Change: Updates for support of Proxy Settings. Enabled -WhatIf processing for many functions.

    Update Date: 8/7/2018
    Purpose/Change: Added support for TLS 1.2

    Update Date: 8/28/2018
    Purpose/Change: Added Update-LTService function

    Update Date: 2/26/2019
    Purpose/Change: Update to support 32-bit execution in 64-bit OS without SYSNATIVE redirection

    Update Date: 9/9/2020
    Purpose/Change: Update to support 64-bit OS without SYSNATIVE redirection (ARM64)
#>

If (-not ($PSVersionTable)) {Write-Warning 'PS1 Detected. PowerShell Version 2.0 or higher is required.';return}
ElseIf ($PSVersionTable.PSVersion.Major -lt 3 ) {Write-Verbose 'PS2 Detected. PowerShell Version 3.0 or higher may be required for full functionality.'}

#Module Version
$ModuleVersion = "1.9.2"
$ModuleGuid='f1f06c84-00c8-11ea-b6e8-000c29aaa7df'

If ($env:PROCESSOR_ARCHITEW6432 -match '64' -and [IntPtr]::Size -ne 8 -and $env:PROCESSOR_ARCHITEW6432 -ne 'ARM64') {
    Write-Warning '32-bit PowerShell session detected on 64-bit OS. Attempting to launch 64-Bit session to process commands.'
    $pshell="${env:windir}\SysNative\WindowsPowershell\v1.0\powershell.exe"
    If (!(Test-Path -Path $pshell)) {
        $pshell="${env:windir}\System32\WindowsPowershell\v1.0\powershell.exe"
        If ($Null -eq ([System.Management.Automation.PSTypeName]'Kernel32.Wow64').Type -or $Null -eq [Kernel32.Wow64].GetMethod('Wow64DisableWow64FsRedirection')) {
            Write-Debug 'Loading WOW64Redirection functions'

            Add-Type -Name Wow64 -Namespace Kernel32 -Debug:$False -MemberDefinition @"
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);

[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool Wow64RevertWow64FsRedirection(ref IntPtr ptr);
"@
        }
        Write-Verbose 'System32 path is redirected. Disabling redirection.'
        [ref]$ptr = New-Object System.IntPtr
        $Result = [Kernel32.Wow64]::Wow64DisableWow64FsRedirection($ptr)
        $FSRedirectionDisabled=$True
    }#End If

    If ($myInvocation.Line) {
        &"$pshell" -NonInteractive -NoProfile $myInvocation.Line
    } Elseif ($myInvocation.InvocationName) {
        &"$pshell" -NonInteractive -NoProfile -File "$($myInvocation.InvocationName)" $args
    } Else {
        &"$pshell" -NonInteractive -NoProfile $myInvocation.MyCommand
    }#End If
    $ExitResult=$LASTEXITCODE

    If ($Null -ne ([System.Management.Automation.PSTypeName]'Kernel32.Wow64').Type -and $Null -ne [Kernel32.Wow64].GetMethod('Wow64DisableWow64FsRedirection') -and $FSRedirectionDisabled -eq $True) {
        [ref]$defaultptr = New-Object System.IntPtr
        $Result = [Kernel32.Wow64]::Wow64RevertWow64FsRedirection($defaultptr)
        Write-Verbose 'System32 path redirection has been re-enabled.'
    }#End If
    Write-Warning 'Exiting 64-bit session. Module will only remain loaded in native 64-bit PowerShell environment.'
    Exit $ExitResult
}#End If

#Ignore SSL errors
If ($Null -eq ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy').Type) {
    Add-Type -Debug:$False @"
        using System.Net;
        using System.Security.Cryptography.X509Certificates;
        public class TrustAllCertsPolicy : ICertificatePolicy {
            public bool CheckValidationResult(
                ServicePoint srvPoint, X509Certificate certificate,
                WebRequest request, int certificateProblem) {
                return true;
            }
        }
"@
}
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
#Enable TLS, TLS1.1, TLS1.2, TLS1.3 in this session if they are available
IF([Net.SecurityProtocolType]::Tls) {[Net.ServicePointManager]::SecurityProtocol=[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls}
IF([Net.SecurityProtocolType]::Tls11) {[Net.ServicePointManager]::SecurityProtocol=[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls11}
IF([Net.SecurityProtocolType]::Tls12) {[Net.ServicePointManager]::SecurityProtocol=[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12}
IF([Net.SecurityProtocolType]::Tls13) {[Net.ServicePointManager]::SecurityProtocol=[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls13}

#region [Functions]-------------------------------------------------------------

Function Get-LTServiceInfo{
<#
.SYNOPSIS
    This function will pull all of the registry data into an object.

.NOTES
    Version:        1.5
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 8/24/2017
    Purpose/Change: Update to use Clear-Variable.

    Update Date: 3/12/2018
    Purpose/Change: Support for ShouldProcess to enable -Confirm and -WhatIf.

    Update Date: 8/28/2018
    Purpose/Change: Remove '~' from server addresses.

    Update Date: 1/19/2019
    Purpose/Change: Improved BasePath value assignment

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact='Low')]
    Param ()

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
        Clear-Variable key,BasePath,exclude,Servers -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        $exclude = "PSParentPath","PSChildName","PSDrive","PSProvider","PSPath"
        $key = $Null
    }#End Begin

    Process{
        If ((Test-Path 'HKLM:\SOFTWARE\LabTech\Service') -eq $False){
            Write-Error "ERROR: Line $(LINENUM): Unable to find information on LTSvc. Make sure the agent is installed."
            Return $Null
        }#End If

        If ($PSCmdlet.ShouldProcess("LTService", "Retrieving Service Registry Values")) {
            Write-Verbose "Checking for LT Service registry keys."
            Try{
                $key = Get-ItemProperty 'HKLM:\SOFTWARE\LabTech\Service' -ErrorAction Stop | Select-Object * -exclude $exclude
                If ($Null -ne $key -and -not ($key|Get-Member -EA 0|Where-Object {$_.Name -match 'BasePath'})) {
                    If ((Test-Path 'HKLM:\SYSTEM\CurrentControlSet\Services\LTService') -eq $True) {
                        Try {
                            $BasePath = Get-Item $( Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\LTService' -ErrorAction Stop|Select-Object -Expand ImagePath | Select-String -Pattern '^[^"][^ ]+|(?<=^")[^"]+'|Select-Object -Expand Matches -First 1 | Select-Object -Expand Value -EA 0 -First 1 ) | Select-Object -Expand DirectoryName -EA 0
                        } Catch {
                            $BasePath = "${env:windir}\LTSVC"
                        }#End Try
                    } Else {
                        $BasePath = "${env:windir}\LTSVC"
                    }#End If
                    Add-Member -InputObject $key -MemberType NoteProperty -Name BasePath -Value $BasePath
                }#End If
                $key.BasePath = [System.Environment]::ExpandEnvironmentVariables($($key|Select-Object -Expand BasePath -EA 0)) -replace '\\\\','\'
                If ($Null -ne $key -and ($key|Get-Member|Where-Object {$_.Name -match 'Server Address'})) {
                    $Servers = ($Key|Select-Object -Expand 'Server Address' -EA 0).Split('|')|ForEach-Object {$_.Trim() -replace '~',''}|Where-Object {$_ -match '.+'}
                    Add-Member -InputObject $key -MemberType NoteProperty -Name 'Server' -Value $Servers -Force
                }#End If
            }#End Try

            Catch{
                Write-Error "ERROR: Line $(LINENUM): There was a problem reading the registry keys. $($Error[0])"
            }#End Catch
        }#End If
    }#End Process

    End{
        If ($?){
            Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
            return $key
        } Else {
            Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
        }#End If
    }#End End
}#End Function Get-LTServiceInfo

Function Get-LTServiceSettings{
<#
.SYNOPSIS
    This function will pull the registry data from HKLM:\SOFTWARE\LabTech\Service\Settings into an object.

.NOTES
    Version:        1.1
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param ()

    Begin{
        Write-Verbose "Checking for registry keys."
        if ((Test-Path 'HKLM:\SOFTWARE\LabTech\Service\Settings') -eq $False){
            Write-Error "ERROR: Unable to find LTSvc settings. Make sure the agent is installed."
        }
        $exclude = "PSParentPath","PSChildName","PSDrive","PSProvider","PSPath"
    }#End Begin

    Process{
        Try{
            Get-ItemProperty HKLM:\SOFTWARE\LabTech\Service\Settings -ErrorAction Stop | Select-Object * -exclude $exclude
        }#End Try

        Catch{
            Write-Error "ERROR: There was a problem reading the registry keys. $($Error[0])"
        }#End Catch
    }#End Process

    End{
        if ($?){
            $key
        }
    }#End End
}#End Function Get-LTServiceSettings

Function Restart-LTService{
<#
.SYNOPSIS
    This function will restart the LabTech Services.

.NOTES
    Version:        1.3
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/13/2018
    Purpose/Change: Added additional debugging output, support for ShouldProcess (-Confirm, -WhatIf)

    Update Date: 3/21/2018
    Purpose/Change: Removed ErrorAction Override

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param()

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End Begin

    Process{
        if (-not (Get-Service 'LTService','LTSvcMon' -ErrorAction SilentlyContinue)) {
            If ($WhatIfPreference -ne $True) {
                Write-Error "ERROR: Line $(LINENUM): Services NOT Found $($Error[0])"
                return
            } Else {
                Write-Error "What-If: Line $(LINENUM): Stopping: Services NOT Found"
                return
            }#End If
        }#End IF
        Try{
            Stop-LTService
        }#End Try
        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error stopping the services. $($Error[0])"
            return
        }#End Catch

        Try{
            Start-LTService
        }#End Try
        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error starting the services. $($Error[0])"
            return
        }#End Catch
    }#End Process

    End{
        If ($WhatIfPreference -ne $True) {
            If ($?) {Write-Output "Services Restarted successfully."}
            Else {$Error[0]}
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Restart-LTService

Function Stop-LTService{
<#
.SYNOPSIS
    This function will stop the LabTech Services.

.DESCRIPTION
    This function will verify that the LabTech services are present then attempt to stop them.
    It will then check for any remaining LabTech processes and kill them.

.NOTES
    Version:        1.3
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/12/2018
    Purpose/Change: Updated Support for ShouldProcess to enable -Confirm and -WhatIf parameters.

    Update Date: 3/21/2018
    Purpose/Change: Removed ErrorAction Override

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param()

    Begin{
        Clear-Variable sw,timeout,svcRun -EA 0 -WhatIf:$False -Confirm:$False -Verbose:$False #Clearing Variables for use
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End Begin

    Process{
        if (-not (Get-Service 'LTService','LTSvcMon' -ErrorAction SilentlyContinue)) {
            If ($WhatIfPreference -ne $True) {
                Write-Error "ERROR: Line $(LINENUM): Services NOT Found $($Error[0])"
                return
            } Else {
                Write-Error "What If: Line $(LINENUM): Stopping: Services NOT Found"
                return
            }#End If
        }#End If
        If ($PSCmdlet.ShouldProcess("LTService, LTSvcMon", "Stop-Service")) {
            $Null=Invoke-LTServiceCommand ('Kill VNC','Kill Trays') -EA 0 -WhatIf:$False -Confirm:$False
            Write-Verbose "Stopping Labtech Services"
            Try{
                ('LTService','LTSvcMon') | Foreach-Object {
                    Try {$Null=& "${env:windir}\system32\sc.exe" stop "$($_)" 2>''}
                    Catch {Write-Output "Error calling sc.exe."}
                }
                $timeout = new-timespan -Minutes 1
                $sw = [diagnostics.stopwatch]::StartNew()
                Write-Host -NoNewline "Waiting for Services to Stop."
                Do {
                    Write-Host -NoNewline '.'
                    Start-Sleep 2
                    $svcRun = ('LTService','LTSvcMon') | Get-Service -EA 0 | Where-Object {$_.Status -ne 'Stopped'} | Measure-Object | Select-Object -Expand Count
                } Until ($sw.elapsed -gt $timeout -or $svcRun -eq 0)
                Write-Host ""
                $sw.Stop()
                if ($svcRun -gt 0) {
                    Write-Verbose "Services did not stop. Terminating Processes after $(([int32]$sw.Elapsed.TotalSeconds).ToString()) seconds."
                }
                Get-Process | Where-Object {@('LTTray','LTSVC','LTSvcMon') -contains $_.ProcessName } | Stop-Process -Force -ErrorAction Stop -Whatif:$False -Confirm:$False
            }#End Try

            Catch{
                Write-Error "ERROR: Line $(LINENUM): There was an error stopping the LabTech processes. $($Error[0])"
                return
            }#End Catch
        }#End If
    }#End Process

    End{
        If ($WhatIfPreference -ne $True) {
            If ($?) {
                If((('LTService','LTSvcMon') | Get-Service -EA 0 | Where-Object {$_.Status -ne 'Stopped'} | Measure-Object | Select-Object -Expand Count) -eq 0){
                    Write-Output "Services Stopped successfully."
                } Else {
                    Write-Warning "WARNING: Line $(LINENUM): Services have not stopped completely."
                }
            } Else {$Error[0]}
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Stop-LTService

Function Start-LTService{
<#
.SYNOPSIS
    This function will start the LabTech Services.

.DESCRIPTION
    This function will verify that the LabTech services are present.
    It will then check for any process that is using the LTTray port (Default 42000) and kill it.
    Next it will start the services.

.NOTES
    Version:        1.5
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 5/11/2017
    Purpose/Change: added check for non standard port number and set services to auto start

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 12/14/2017
    Purpose/Change: Will increment the tray port if a conflict is detected.

    Update Date: 2/1/2018
    Purpose/Change: Added support for -WhatIf. Added Service Control Command to request agent check-in immediately after startup.

    Update Date: 3/21/2018
    Purpose/Change: Removed ErrorAction Override

    .LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param()

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
        #Identify processes that are using the tray port
        [array]$processes = @()
        $Port = (Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand TrayPort -EA 0)
        if (-not ($Port)) {$Port = "42000"}
        $startedSvcCount=0
    }#End Begin

    Process{
        If (-not (Get-Service 'LTService','LTSvcMon' -ErrorAction SilentlyContinue)) {
            If ($WhatIfPreference -ne $True) {
                Write-Error "ERROR: Line $(LINENUM): Services NOT Found $($Error[0])"
                return
            } Else {
                Write-Error "What If: Line $(LINENUM): Stopping: Services NOT Found"
                return
            }#End If
        }#End If
        Try{
            If((('LTService') | Get-Service -EA 0 | Where-Object {$_.Status -eq 'Stopped'} | Measure-Object | Select-Object -Expand Count) -gt 0) {
                Try {$netstat=& "${env:windir}\system32\netstat.exe" -a -o -n 2>'' | Select-String -Pattern " .*[0-9\.]+:$($Port).*[0-9\.]+:[0-9]+ .*?([0-9]+)" -EA 0}
                Catch {Write-Output "Error calling netstat.exe."; $netstat=$null}
                Foreach ($line in $netstat){
                    $processes += ($line -split ' {4,}')[-1]
                }#End Foreach
                $processes = $processes | Where-Object {$_ -gt 0 -and $_ -match '^\d+$'}| Sort-Object | Get-Unique
                If ($processes) {
                    Foreach ($proc in $processes){
                        Write-Output "Process ID:$proc is using port $Port. Killing process."
                        Try{Stop-Process -ID $proc -Force -Verbose -EA Stop}
                        Catch {
                            Write-Warning "WARNING: Line $(LINENUM): There was an issue killing the following process: $proc"
                            Write-Warning "WARNING: Line $(LINENUM): This generally means that a 'protected application' is using this port."
                            $newPort = [int]$port + 1
                            if($newPort -gt 42009) {$newPort = 42000}
                            Write-Warning "WARNING: Line $(LINENUM): Setting tray port to $newPort."
                            New-ItemProperty -Path "HKLM:\Software\Labtech\Service" -Name TrayPort -PropertyType String -Value $newPort -Force -WhatIf:$False -Confirm:$False | Out-Null
                        }#End Catch
                    }#End Foreach
                }#End If
            }#End If
            If ($PSCmdlet.ShouldProcess("LTService, LTSvcMon", "Start Service")) {
                @('LTService','LTSvcMon') | ForEach-Object {
                    If (Get-Service $_ -EA 0) {
                        Set-Service $_ -StartupType Automatic -EA 0 -Confirm:$False -WhatIf:$False
                        $Null=& "${env:windir}\system32\sc.exe" start "$($_)" 2>''
                        $startedSvcCount++
                        Write-Debug "Line $(LINENUM): Executed Start Service for $($_)"
                    }#End If
                }#End ForEach-Object
            }#End If
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error starting the LabTech services. $($Error[0])"
            return
        }#End Catch
    }#End Process

    End{
        If ($WhatIfPreference -ne $True) {
            If ($?){
                $svcnotRunning = ('LTService') | Get-Service -EA 0 | Where-Object {$_.Status -ne 'Running'} | Measure-Object | Select-Object -Expand Count
                If ($svcnotRunning -gt 0 -and $startedSvcCount -eq 2) {
                    $timeout = new-timespan -Minutes 1
                    $sw = [diagnostics.stopwatch]::StartNew()
                    Write-Host -NoNewline "Waiting for Services to Start."
                    Do {
                        Write-Host -NoNewline '.'
                        Start-Sleep 2
                        $svcnotRunning = ('LTService') | Get-Service -EA 0 | Where-Object {$_.Status -ne 'Running'} | Measure-Object | Select-Object -Expand Count
                    } Until ($sw.elapsed -gt $timeout -or $svcnotRunning -eq 0)
                    Write-Host ""
                    $sw.Stop()
                }#End If
                If ($svcnotRunning -eq 0) {
                    Write-Output "Services Started successfully."
                    $Null=Invoke-LTServiceCommand 'Send Status' -EA 0 -Confirm:$False
                } ElseIf ($startedSvcCount -gt 0) {
                    Write-Output "Service Start was issued but LTService has not reached Running state."
                } Else {
                    Write-Output "Service Start was not issued."
                }#End If
            }
            Else{
                $($Error[0])
            }#End If
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Start-LTService

Function Uninstall-LTService{
<#
.SYNOPSIS
    This function will uninstall the LabTech agent from the machine.

.DESCRIPTION
    This function will stop all the LabTech services. It will then download the current agent install MSI and issue an uninstall command.
    It will then download and run Agent_Uninstall.exe from the LabTech server. It will then scrub any remaining file/registry/service data.

.PARAMETER Server
    This is the URL to your LabTech server.
    Example: https://lt.domain.com
    This is used to download the uninstall utilities.
    If no server is provided the uninstaller will use Get-LTServiceInfo to get the server address.

.PARAMETER Backup
    This will run a 'New-LTServiceBackup' before uninstalling.

.PARAMETER Force
    This will force operation on an agent detected as a probe.

.EXAMPLE
    Uninstall-LTService
    This will uninstall the LabTech agent using the server address in the registry.

.EXAMPLE
    Uninstall-LTService -Server 'https://lt.domain.com'
    This will uninstall the LabTech agent using the provided server URL to download the uninstallers.

.NOTES
    Version:        1.9.1
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 6/10/2017
    Purpose/Change: Updates for pipeline input, support for multiple servers

    Update Date: 6/24/2017
    Purpose/Change: Update to detect Server Version and use updated URL format for LabTech 11 Patch 13.

    Update Date: 8/24/2017
    Purpose/Change: Update to use Clear-Variable. Modifications to Folder and Registry Delete steps. Additional Debugging.

    Update Date: 1/26/2017
    Purpose/Change: Added support for Proxy Server for Download and Installation steps.

    Update Date: 3/12/2018
    Purpose/Change: Added detection of "Probe" enabled agent.
    Added support for -Force parameter to override probe detection.
    Updated support of -WhatIf parameter.
    Added minimum size requirement for agent installer to detect and skip a bad file download.

    Update Date: 10/18/2018
    Purpose/Change: Added minimum size requirement for agent uninstaller exe to detect and skip a bad file download.
    Uninstall will proceed even if the agent uninstaller exe cannot be downloaded.

    Update Date: 1/21/2019
    Purpose/Change: Minor bugfixes/adjustments.
    Allow single label server name.

    Update Date: 2/28/2019
    Purpose/Change: Update to try both http and https method if not specified for Server

    Update Date: 6/22/2020
    Purpose/Change: Use unique pathname for Uninstall MSI, add Uninstaller EXE fallback

    Update Date: 7/25/2024
    Purpose/Change: Uninstaller EXE fallback even if server is unavailable

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [AllowNull()]
        [string[]]$Server,
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [switch]$Backup,
        [switch]$Force
    )

    Begin{
        Clear-Variable Executables,BasePath,reg,regs,installer,installerTest,installerResult,LTSI,uninstaller,uninstallerTest,uninstallerResult,xarg,Svr,SVer,SvrVer,SvrVerCheck,GoodServer,AlternateServer,Item -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"

        If (-not ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()|Select-Object -Expand groups -EA 0) -match 'S-1-5-32-544'))) {
            Throw "Line $(LINENUM): Needs to be ran as Administrator"
        }

        $LTSI = Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False
        If (($LTSI) -and ($LTSI|Select-Object -Expand Probe -EA 0) -eq '1') {
            If ($Force -eq $True) {
                Write-Output "Probe Agent Detected. UnInstall Forced."
            } Else {
                Write-Error -Exception [System.OperationCanceledException]"Line $(LINENUM): Probe Agent Detected. UnInstall Denied." -ErrorAction Stop
            }#End If
        }#End If

        If ($Backup){
            If ( $PSCmdlet.ShouldProcess("LTService","Backup Current Service Settings") ) {
                New-LTServiceBackup
            }#End If
        }#End If

        $BasePath = $(Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand BasePath -EA 0)
        If (-not ($BasePath)) {$BasePath = "${env:windir}\LTSVC"}
        $UninstallBase="${env:windir}\Temp"
        $UninstallEXE='Agent_Uninstall.exe'
        $UninstallMSI='RemoteAgent.msi'

        New-PSDrive HKU Registry HKEY_USERS -ErrorAction SilentlyContinue -WhatIf:$False -Confirm:$False -Debug:$False| Out-Null
        $regs = @( 'Registry::HKEY_LOCAL_MACHINE\Software\LabTechMSP',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\LabTech\Service',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\LabTech\LabVNC',
            'Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\LabTech\Service',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Managed\\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\D1003A85576B76D45A1AF09A0FC87FAC\InstallProperties',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{58A3001D-B675-4D67-A5A1-0FA9F08CF7CA}',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{3426921d-9ad5-4237-9145-f15dee7e3004}',
            'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\Appmgmt\{40bf8c82-ed0d-4f66-b73e-58a3d7ab6582}',
            'Registry::HKEY_CLASSES_ROOT\Installer\Dependencies\{3426921d-9ad5-4237-9145-f15dee7e3004}',
            'Registry::HKEY_CLASSES_ROOT\Installer\Dependencies\{3F460D4C-D217-46B4-80B6-B5ED50BD7CF5}',
            'Registry::HKEY_CLASSES_ROOT\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F',
            'Registry::HKEY_CLASSES_ROOT\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{09DF1DCA-C076-498A-8370-AD6F878B6C6A}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{15DD3BF6-5A11-4407-8399-A19AC10C65D0}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{3C198C98-0E27-40E4-972C-FDC656EC30D7}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{459C65ED-AA9C-4CF1-9A24-7685505F919A}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{7BE3886B-0C12-4D87-AC0B-09A5CE4E6BD6}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{7E092B5C-795B-46BC-886A-DFFBBBC9A117}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{9D101D9C-18CC-4E78-8D78-389E48478FCA}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{B0B8CDD6-8AAA-4426-82E9-9455140124A1}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{B1B00A43-7A54-4A0F-B35D-B4334811FAA4}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{BBC521C8-2792-43FE-9C91-CCA7E8ACBCC9}',
            'Registry::HKEY_CLASSES_ROOT\CLSID\{C59A1D54-8CD7-4795-AEDD-F6F6E2DE1FE7}',
            'Registry::HKEY_CLASSES_ROOT\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F',
            'Registry::HKEY_CLASSES_ROOT\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC',
            'Registry::HKEY_CURRENT_USER\SOFTWARE\LabTech\Service',
            'Registry::HKEY_CURRENT_USER\SOFTWARE\LabTech\LabVNC',
            'Registry::HKEY_CURRENT_USER\Software\Microsoft\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F',
            'HKU:\*\Software\Microsoft\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F'
        )

        $xarg = "/x ""$UninstallBase\$UninstallMSI"" /qn"
    }#End Begin

    Process{
        If (-not ($Server)){
            $Server = Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand 'Server' -EA 0
        }
        If (-not ($Server)){
            $Server = Read-Host -Prompt 'Provide the URL to your LabTech server (https://lt.domain.com)'
        }
        $Server=ForEach ($Svr in $Server) {If (($Svr)) {If ($Svr -notmatch 'https?://.+') {"https://$($Svr)"}; $Svr}}
        ForEach ($Svr in $Server) {
            If (-not ($GoodServer)) {
                If ($Svr -match '^(https?://)?(([12]?[0-9]{1,2}\.){3}[12]?[0-9]{1,2}|[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*)$') {
                    Try{
                        If ($Svr -notmatch 'https?://.+') {$Svr = "http://$($Svr)"}
                        $SvrVerCheck = "$($Svr)/LabTech/Agent.aspx"
                        Write-Debug "Line $(LINENUM): Testing Server Response and Version: $SvrVerCheck"
                        $SvrVer = $Script:LTServiceNetWebClient.DownloadString($SvrVerCheck)

                        Write-Debug "Line $(LINENUM): Raw Response: $SvrVer"
                        $SVer = $SvrVer|select-string -pattern '(?<=[|]{6})[0-9]{1,3}\.[0-9]{1,3}'|ForEach-Object {$_.matches}|Select-Object -Expand value -EA 0
                        If ($Null -eq ($SVer)) {
                            Write-Verbose "Unable to test version response from $($Svr)."
                            Continue
                        }
                        $installer = "$($Svr)/LabTech/Service/LabTechRemoteAgent.msi"
                        $installerTest = [System.Net.WebRequest]::Create($installer)
                        If (($Script:LTProxy.Enabled) -eq $True) {
                            Write-Debug "Line $(LINENUM): Proxy Configuration Needed. Applying Proxy Settings to request."
                            $installerTest.Proxy=$Script:LTWebProxy
                        }#End If
                        $installerTest.KeepAlive=$False
                        $installerTest.ProtocolVersion = '1.0'
                        $installerResult = $installerTest.GetResponse()
                        $installerTest.Abort()
                        If ($installerResult.StatusCode -ne 200) {
                            Write-Warning "WARNING: Line $(LINENUM): Unable to download $UninstallMSI from server $($Svr)."
                            Continue
                        } Else {
                            If ($PSCmdlet.ShouldProcess("$installer", "DownloadFile")) {
                                Write-Debug "Line $(LINENUM): Downloading $UninstallMSI from $installer"
                                $Script:LTServiceNetWebClient.DownloadFile($installer,"$UninstallBase\$UninstallMSI")
                                If ((Test-Path "$UninstallBase\$UninstallMSI")) {
                                    If (!((Get-Item "$UninstallBase\$UninstallMSI" -EA 0).length/1KB -gt 1234)) {
                                        Write-Warning "WARNING: Line $(LINENUM): $UninstallMSI size is below normal. Removing suspected corrupt file."
                                        Remove-Item "$UninstallBase\$UninstallMSI" -ErrorAction SilentlyContinue -Force -Confirm:$False
                                        Continue
                                    }#End If
                                }#End If
                            }#End If
                        }#End If

                        #Using $SVer results gathered above.
                        If ([System.Version]$SVer -ge [System.Version]'110.374') {
                            #New Style Download Link starting with LT11 Patch 13 - The Agent Uninstaller URI has changed.
                            $uninstaller = "$($Svr)/LabTech/Service/LabUninstall.exe"
                        } Else {
                            #Original Uninstaller URL
                            $uninstaller = "$($Svr)/LabTech/Service/LabUninstall.exe"
                        }
                        $uninstallerTest = [System.Net.WebRequest]::Create($uninstaller)
                        If (($Script:LTProxy.Enabled) -eq $True) {
                            Write-Debug "Line $(LINENUM): Proxy Configuration Needed. Applying Proxy Settings to request."
                            $uninstallerTest.Proxy=$Script:LTWebProxy
                        }#End If
                        $uninstallerTest.KeepAlive=$False
                        $uninstallerTest.ProtocolVersion = '1.0'
                        $uninstallerResult = $uninstallerTest.GetResponse()
                        $uninstallerTest.Abort()
                        If ($uninstallerResult.StatusCode -ne 200) {
                            Write-Warning "WARNING: Line $(LINENUM): Unable to download Agent_Uninstall from server."
                            Continue
                        } Else {
                            #Download $UninstallEXE
                            If ($PSCmdlet.ShouldProcess("$uninstaller", "DownloadFile")) {
                                Write-Debug "Line $(LINENUM): Downloading $UninstallEXE from $uninstaller"
                                $Script:LTServiceNetWebClient.DownloadFile($uninstaller,"$UninstallBase\$UninstallEXE")
                                If ((Test-Path "$UninstallBase\$UninstallEXE") -and !((Get-Item "$UninstallBase\$UninstallEXE" -EA 0).length/1KB -gt 80)) {
                                    Write-Warning "WARNING: Line $(LINENUM): $UninstallEXE size is below normal. Removing suspected corrupt file."
                                    Remove-Item "$UninstallBase\$UninstallEXE" -ErrorAction SilentlyContinue -Force -Confirm:$False
                                    Continue
                                }#End If
                            }#End If
                        }#End If
                        If ($WhatIfPreference -eq $True) {
                            $GoodServer = $Svr
                        } ElseIf ((Test-Path "$UninstallBase\$UninstallMSI") -and (Test-Path "$UninstallBase\$UninstallEXE")) {
                            Write-Verbose "Successfully downloaded files from $($Svr)."
                            $GoodServer = $Svr
                        } Else {
                            Write-Warning "WARNING: Line $(LINENUM): Error encountered downloading from $($Svr). Uninstall file(s) could not be received."
                            Continue
                        }#End If
                    }#End Try
                    Catch {
                        Write-Warning "WARNING: Line $(LINENUM): Error encountered downloading from $($Svr)."
                        Continue
                    }
                } ElseIf ($Svr) {
                    Write-Verbose "Server address $($Svr) is not formatted correctly. Example: https://lt.domain.com"
                }#End If
            } Else {
                Write-Debug "Line $(LINENUM): Server $($GoodServer) has been selected."
                Write-Verbose "Server has already been selected - Skipping $($Svr)."
            }#End If
        }#End Foreach
    }#End Process

    End{
        If (-not ($GoodServer -match 'https?://.+')) {
            #Download $UninstallEXE
            $uninstaller='https://s3.amazonaws.com/assets-cp/assets/Agent_Uninstall.exe'
            If ($PSCmdlet.ShouldProcess("$uninstaller", "DownloadFile")) {
                Write-Debug "Line $(LINENUM): Downloading $UninstallEXE from $uninstaller"
                $Script:LTServiceNetWebClient.DownloadFile($uninstaller,"$UninstallBase\$UninstallEXE")
                If ((Test-Path "$UninstallBase\$UninstallEXE")) {
                    If(((Get-Item "$UninstallBase\$UninstallEXE" -EA 0).length/1KB -gt 80)) {
                        $GoodServer='https://s3.amazonaws.com'
                    } Else {
                        Write-Warning "Line $(LINENUM): $UninstallEXE size is below normal. Removing suspected corrupt file."
                        Remove-Item "$UninstallBase\$UninstallEXE" -ErrorAction SilentlyContinue -Force -Confirm:$False   
                    }#End If
                }#End If
            }#End If
        }
        If ($GoodServer -match 'https?://.+') {
            Try{
                Write-Output "Starting Uninstall."

                Try { Stop-LTService -ErrorAction SilentlyContinue } Catch {}

                #Kill all running processes from %ltsvcdir%
                If (Test-Path $BasePath){
                    $Executables = (Get-ChildItem $BasePath -Filter *.exe -Recurse -ErrorAction SilentlyContinue|Select-Object -Expand FullName)
                    If ($Executables) {
                        Write-Verbose "Terminating LabTech Processes from $($BasePath) if found running: $(($Executables) -replace [Regex]::Escape($BasePath),'' -replace '^\\','')"
                        Get-Process | Where-Object {$Executables -contains $_.Path } | ForEach-Object {
                            Write-Debug "Line $(LINENUM): Terminating Process $($_.ProcessName)"
                            $($_) | Stop-Process -Force -ErrorAction SilentlyContinue
                        }
                        Get-ChildItem $BasePath -Filter labvnc.exe -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction 0
                    }#End If

                    If ($PSCmdlet.ShouldProcess("$($BasePath)\wodVPN.dll", "Unregister DLL")) {
                        #Unregister DLL
                        Write-Debug "Line $(LINENUM): Executing Command ""regsvr32.exe /u $($BasePath)\wodVPN.dll /s"""
                        Try {& "${env:windir}\system32\regsvr32.exe" /u "$($BasePath)\wodVPN.dll" /s 2>''}
                        Catch {Write-Output "Error calling regsvr32.exe."}
                    }#End If
                }#End If

                If ($PSCmdlet.ShouldProcess("msiexec.exe $($xarg)", "Execute MSI Uninstall")) {
                    If ((Test-Path "$UninstallBase\$UninstallMSI")) {
                        #Run MSI uninstaller for current installation
                        Write-Verbose "Launching MSI Uninstall."
                        Write-Debug "Line $(LINENUM): Executing Command ""msiexec.exe $($xarg)"""
                        Start-Process -Wait -FilePath "${env:windir}\system32\msiexec.exe" -ArgumentList $xarg -WorkingDirectory $UninstallBase
                        Start-Sleep -Seconds 5
                    } Else {
                        Write-Verbose "WARNING: $UninstallBase\$UninstallMSI was not found."
                    }
                }#End If

                If ($PSCmdlet.ShouldProcess("$UninstallBase\$UninstallEXE", "Execute Agent Uninstall")) {
                    If ((Test-Path "$UninstallBase\$UninstallEXE")) {
                        #Run $UninstallEXE
                        Write-Verbose "Launching Agent Uninstaller"
                        Write-Debug "Line $(LINENUM): Executing Command ""$UninstallBase\$UninstallEXE"""
                        Start-Process -Wait -FilePath "$UninstallBase\$UninstallEXE" -WorkingDirectory $UninstallBase
                        Start-Sleep -Seconds 5
                    } Else {
                        Write-Verbose "WARNING: $UninstallBase\$UninstallEXE was not found."
                    }
                }#End If

                Write-Verbose "Removing Services if found."
                #Remove Services
                @('LTService','LTSvcMon','LabVNC') | ForEach-Object {
                    If (Get-Service $_ -EA 0) {
                        If ( $PSCmdlet.ShouldProcess("$($_)","Remove Service") ) {
                            Write-Debug "Line $(LINENUM): Removing Service: $($_)"
                            Try {& "${env:windir}\system32\sc.exe" delete "$($_)" 2>''}
                            Catch {Write-Output "Error calling sc.exe."}
                        }#End If
                    }#End If
                }#End ForEach-Object

                Write-Verbose "Cleaning Files remaining if found."
                #Remove %ltsvcdir% - Depth First Removal, First by purging files, then Removing Folders, to get as much removed as possible if complete removal fails
                @($BasePath, "${env:windir}\temp\_ltupdate", "${env:windir}\temp\_ltupdate") | foreach-object {
                    If ((Test-Path "$($_)" -EA 0)) {
                        If ( $PSCmdlet.ShouldProcess("$($_)","Remove Folder") ) {
                            Write-Debug "Line $(LINENUM): Removing Folder: $($_)"
                            Try {
                                Get-ChildItem -Path $_ -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { ($_.psiscontainer) } | foreach-object { Get-ChildItem -Path "$($_.FullName)" -EA 0 | Where-Object { -not ($_.psiscontainer) } | Remove-Item -Force -ErrorAction SilentlyContinue -Confirm:$False -WhatIf:$False }
                                Get-ChildItem -Path $_ -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { ($_.psiscontainer) } | Sort-Object { $_.fullname.length } -Descending | Remove-Item -Force -ErrorAction SilentlyContinue -Recurse -Confirm:$False -WhatIf:$False
                                Remove-Item -Recurse -Force -Path $_ -ErrorAction SilentlyContinue -Confirm:$False -WhatIf:$False
                            } Catch {}
                        }#End If
                    }#End If
                }#End Foreach-Object

                Write-Verbose "Cleaning Registry Keys if found."
                #Remove all registry keys - Depth First Value Removal, then Key Removal, to get as much removed as possible if complete removal fails
                Foreach ($reg in $regs) {
                    If ((Test-Path "$($reg)" -EA 0)) {
                        Write-Debug "Line $(LINENUM): Found Registry Key: $($reg)"
                        If ( $PSCmdlet.ShouldProcess("$($Reg)","Remove Registry Key") ) {
                            Try {
                                Get-ChildItem -Path $reg -Recurse -Force -ErrorAction SilentlyContinue | Sort-Object { $_.name.length } -Descending | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue -Confirm:$False -WhatIf:$False
                                Remove-Item -Recurse -Force -Path $reg -ErrorAction SilentlyContinue -Confirm:$False -WhatIf:$False
                            } Catch {}
                        }#End If
                    }#End If
                }#End Foreach
            }#End Try

            Catch{
                Write-Error "ERROR: Line $(LINENUM): There was an error during the uninstall process. $($Error[0])" -ErrorAction Stop
            }#End Catch

            If ($WhatIfPreference -ne $True) {
                If ($?){
                    #Post Uninstall Check
                    If((Test-Path "${env:windir}\ltsvc") -or (Test-Path "${env:windir}\temp\_ltupdate") -or (Test-Path registry::HKLM\Software\LabTech\Service) -or (Test-Path registry::HKLM\Software\WOW6432Node\Labtech\Service)){
                        Start-Sleep -Seconds 10
                    }#End If
                    If((Test-Path "${env:windir}\ltsvc") -or (Test-Path "${env:windir}\temp\_ltupdate") -or (Test-Path registry::HKLM\Software\LabTech\Service) -or (Test-Path registry::HKLM\Software\WOW6432Node\Labtech\Service)){
                        Write-Error "ERROR: Line $(LINENUM): Remnants of previous install still detected after uninstall attempt. Please reboot and try again."
                    } Else {
                        Write-Output "LabTech has been successfully uninstalled."
                    }#End If
                } Else {
                    $($Error[0])
                }#End If
            }#End If
        } ElseIf ($WhatIfPreference -ne $True) {
            Write-Error "ERROR: Line $(LINENUM): No valid server was reached to use for the uninstall." -ErrorAction Stop
        }#End If

        If ($WhatIfPreference -ne $True) {
            #Cleanup uninstall files
            Remove-Item "$UninstallBase\$UninstallEXE","$UninstallBase\$UninstallMSI" -ErrorAction SilentlyContinue -Force -Confirm:$False
        }#End If

        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Uninstall-LTService

Function Install-LTService{
<#
.SYNOPSIS
    This function will install the LabTech agent on the machine.

.DESCRIPTION
    This function will install the LabTech agent on the machine with the specified server/password/location.

.PARAMETER Server
    This is the URL to your LabTech server.
    example: https://lt.domain.com
    This is used to download the installation files.
    (Get-LTServiceInfo|Select-Object -Expand 'Server Address' -ErrorAction SilentlyContinue)

.PARAMETER ServerPassword
    This is the server password that agents use to authenticate with the LabTech server.
    SELECT SystemPassword FROM config;

.PARAMETER InstallerToken
    Permits use of installer tokens for customized MSI downloads. (Other installer types are not supported)

.PARAMETER LocationID
    This is the LocationID of the location that the agent will be put into.
    (Get-LTServiceInfo).LocationID

.PARAMETER TrayPort
    This is the port LTSvc.exe listens on for communication with LTTray processes.

.PARAMETER Rename
    This will call Rename-LTAddRemove after the install.

.PARAMETER Hide
    This will call Hide-LTAddRemove after the install.

.PARAMETER SkipDotNet
    This will disable the error checking for the .NET 3.5 and .NET 2.0 frameworks during the install process.

.PARAMETER Force
    This will disable some of the error checking on the install process.

.PARAMETER NoWait
    This will skip the ending health check for the install process.
    The function will exit once the installer has completed.

.EXAMPLE
    Install-LTService -Server https://lt.domain.com -Password 'plain text pass' -LocationID 42
    This will install the LabTech agent using the provided Server URL, Password, and LocationID.

.NOTES
    Version:        2.1
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 6/10/2017
    Purpose/Change: Updates for pipeline input, support for multiple servers

    Update Date: 6/24/2017
    Purpose/Change: Update to detect Server Version and use updated URL format for LabTech 11 Patch 13.

    Update Date: 8/24/2017
    Purpose/Change: Update to use Clear-Variable. Additional Debugging.

    Update Date: 8/29/2017
    Purpose/Change: Additional Debugging.

    Update Date: 9/7/2017
    Purpose/Change: Support for ShouldProcess to enable -Confirm and -WhatIf.

    Update Date: 1/26/2018
    Purpose/Change: Added support for Proxy Server for Download and Installation steps.

    Update Date: 2/13/2018
    Purpose/Change: Added -TrayPort parameter.

    Update Date: 3/13/2018
    Purpose/Change: Added -NoWait parameter.
    Added minimum size requirement for agent installer to detect and skip a bad file download.

    Update Date: 6/5/2018
    Purpose/Change: Added -SkipDotNet parameter.
    Allows for skipping of .NET 3.5 and 2.0 framework checks for installing on OS with .NET 4.0+ already installed

    Update Date: 1/21/2019
    Purpose/Change: Minor bugfixes/adjustments.
    Allow single label server name, accept Agent ID 1 as valid.

    Update Date: 2/28/2019
    Purpose/Change: Update to try both http and https methods if not specified for Server

    Update Date: 12/28/2019
    Purpose/Change: Handle .NET 3.5 in pending state, accept .NET 4.0+ or higher with -Force parameter

    Update Date: 6/10/2020
    Purpose/Change: Remove Deployment.aspx dependance

    Update Date: 6/11/2020
    Purpose/Change: Update to work with or without Deployment.aspx

    Update Date: 6/20/2020
    Purpose/Change: Added -InstallerToken parameter. Added Installer cleanup.
    Allows for MSI download and installation without -ServerPassword parameter
    Remove unprotected references to the Server Password

    Update Date: 1/3/2021
    Purpose/Change: Escaped double quotes in -ServerPassword parameter
    Prevents MSI install from hanging perpetually

    Update Date: 7/25/2024
    Purpose/Change: Added support for MSI/ZIP Bundle method used by 2024 Patch 7
    Allowed '-' character in InstallerToken

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName = 'deployment')]
    Param(
        [Parameter(ParameterSetName = 'deployment')]
        [Parameter(ParameterSetName = 'installertoken')]
        [Parameter(ValueFromPipelineByPropertyName = $true, Mandatory=$True)]
        [string[]]$Server,
        [Parameter(ParameterSetName = 'deployment')]
        [Parameter(ValueFromPipelineByPropertyName = $True)]
        [AllowNull()]
        [Alias("Password")]
        [string]$ServerPassword,
        [Parameter(ParameterSetName = 'installertoken')]
        [ValidatePattern('(?s:^[0-9a-z\-]+$)')]
        [string]$InstallerToken,
        [Parameter(ValueFromPipelineByPropertyName = $True)]
        [AllowNull()]
        [int]$LocationID,
        [Parameter(ValueFromPipelineByPropertyName = $True)]
        [AllowNull()]
        [int]$TrayPort,
        [Parameter()]
        [AllowNull()]
        [string]$Rename,
        [switch]$Hide,
        [switch]$SkipDotNet,
        [switch]$Force,
        [switch]$NoWait
    )

    Begin{
        Clear-Variable DotNET,OSVersion,PasswordArg,Result,logpath,logfile,curlog,installer,installerTest,installerResult,GoodServer,GoodTrayPort,TestTrayPort,Svr,SVer,SvrVer,SvrVerCheck,iarg,timeout,sw,tmpLTSI -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"

        If (!($Force)) {
            If (Get-Service 'LTService','LTSvcMon' -ErrorAction SilentlyContinue) {
                If ($WhatIfPreference -ne $True) {
                    Write-Error "ERROR: Line $(LINENUM): Services are already installed." -ErrorAction Stop
                } Else {
                    Write-Error "ERROR: Line $(LINENUM): What if: Stopping: Services are already installed." -ErrorAction Stop
                }#End If
            }#End If
        }#End If

        If (-not ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()|Select-Object -Expand groups -EA 0) -match 'S-1-5-32-544'))) {
            Throw "Needs to be ran as Administrator"
        }

        If (!$SkipDotNet){
            $DotNET = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse -EA 0 | Get-ItemProperty -name Version,Release -EA 0 | Where-Object { $_.PSChildName -match '^(?!S)\p{L}'} | Select-Object -ExpandProperty Version -EA 0
            If (-not ($DotNet -like '3.5.*')){
                Write-Output ".NET Framework 3.5 installation needed."
                #Install-WindowsFeature Net-Framework-Core
                $OSVersion = [System.Environment]::OSVersion.Version

                If ([version]$OSVersion -gt [version]'6.2'){
                    Try{
                        If ( $PSCmdlet.ShouldProcess('NetFx3', 'Enable-WindowsOptionalFeature') ) {
                            $Install = Get-WindowsOptionalFeature -Online -FeatureName 'NetFx3'
                            If (!($Install.State -eq 'EnablePending')) {
                                $Install = Enable-WindowsOptionalFeature -Online -FeatureName 'NetFx3' -All -NoRestart
                            }
                            If ($Install.RestartNeeded -or $Install.State -eq 'EnablePending') {
                                Write-Output ".NET Framework 3.5 installed but a reboot is needed."
                            }
                        }
                    }
                    Catch{
                        Write-Error "ERROR: Line $(LINENUM): .NET 3.5 install failed." -ErrorAction Continue
                        If (!($Force)) { Write-Error ("Line $(LINENUM):",$Install) -ErrorAction Stop }
                    }
                }
                ElseIf ([version]$OSVersion -gt [version]'6.1'){
                    If ( $PSCmdlet.ShouldProcess("NetFx3", "Add Windows Feature") ) {
                        Try {$Result=& "${env:windir}\system32\Dism.exe" /English /NoRestart /Online /Enable-Feature /FeatureName:NetFx3 2>''}
                        Catch {Write-Output "Error calling Dism.exe."; $Result=$Null}
                        Try {$Result=& "${env:windir}\system32\Dism.exe" /English /Online /Get-FeatureInfo /FeatureName:NetFx3 2>''}
                        Catch {Write-Output "Error calling Dism.exe."; $Result=$Null}
                        If ($Result -contains 'State : Enabled'){
                            Write-Warning "WARNING: Line $(LINENUM): .Net Framework 3.5 has been installed and enabled."
                        } ElseIf ($Result -contains 'State : Enable Pending'){
                            Write-Warning "WARNING: Line $(LINENUM): .Net Framework 3.5 installed but a reboot is needed."
                        } Else {
                            Write-Error "ERROR: Line $(LINENUM): .NET Framework 3.5 install failed." -ErrorAction Continue
                            If (!($Force)) { Write-Error ("ERROR: Line $(LINENUM):",$Result) -ErrorAction Stop }
                        }#End If
                    }#End If
                }#End If

                $DotNET = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse | Get-ItemProperty -name Version -EA 0 | Where-Object{ $_.PSChildName -match '^(?!S)\p{L}'} | Select-Object -ExpandProperty Version
            }#End If

            If (-not ($DotNet -like '3.5.*')){
                If (($Force)) {
                    If ($DotNet -match '(?m)^[2-4].\d'){
                        Write-Error "ERROR: Line $(LINENUM): .NET 3.5 is not detected and could not be installed." -ErrorAction Continue
                    } Else {
                        Write-Error "ERROR: Line $(LINENUM): .NET 2.0 or greater is not detected and could not be installed." -ErrorAction Stop
                    }#End If
                } Else {
                    Write-Error "ERROR: Line $(LINENUM): .NET 3.5 is not detected and could not be installed." -ErrorAction Stop
                }#End If
            }#End If
        }#End If

        $InstallBase="${env:windir}\Temp\LabTech"
        $logfile = "LTAgentInstall"
        $curlog = "$($InstallBase)\$($logfile).log"
        If ($ServerPassword -match '"') {$ServerPassword=$ServerPassword.Replace('"','""')}

        If (-not (Test-Path -PathType Container -Path "$InstallBase\Installer" )){
            New-Item "$InstallBase\Installer" -type directory -ErrorAction SilentlyContinue | Out-Null
        }#End if
        If ((Test-Path -PathType Leaf -Path $($curlog))){
            If ($PSCmdlet.ShouldProcess("$($curlog)","Rotate existing log file")){
                Get-Item -LiteralPath $curlog -EA 0 | Where-Object {$_} | Foreach-Object {
                    Rename-Item -Path $($_|Select-Object -Expand FullName -EA 0) -NewName "$($logfile)-$(Get-Date $($_|Select-Object -Expand LastWriteTime -EA 0) -Format 'yyyyMMddHHmmss').log" -Force -Confirm:$False -WhatIf:$False
                    Remove-Item -Path $($_|Select-Object -Expand FullName -EA 0) -Force -EA 0 -Confirm:$False -WhatIf:$False
                }#End Foreach-Object
            }#End If
        }#End If
    }#End Begin

    Process{
        If (-not ($LocationID -or $PSCmdlet.ParameterSetName -eq 'installertoken')){
            $LocationID = "1"
        }
        If (-not ($TrayPort) -or -not ($TrayPort -ge 1 -and $TrayPort -le 65535)){
            $TrayPort = "42000"
        }
        $Server=ForEach ($Svr in $Server) {If ($Svr -notmatch 'https?://.+') {"https://$($Svr)"}; $Svr}
        ForEach ($Svr in $Server) {
            If (-not ($GoodServer)) {
                If ($Svr -match '^(https?://)?(([12]?[0-9]{1,2}\.){3}[12]?[0-9]{1,2}|[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*)$') {
                    $InstallMSI='Agent_Install.msi'
                    If ($Svr -notmatch 'https?://.+') {$Svr = "http://$($Svr)"}
                    Try {
                        $SvrVerCheck = "$($Svr)/LabTech/Agent.aspx"
                        Write-Debug "Line $(LINENUM): Testing Server Response and Version: $SvrVerCheck"
                        $SvrVer = $Script:LTServiceNetWebClient.DownloadString($SvrVerCheck)
                        Write-Debug "Line $(LINENUM): Raw Response: $SvrVer"
                        $SVer = $SvrVer|select-string -pattern '(?<=[|]{6})[0-9]{1,3}\.[0-9]{1,3}'|ForEach-Object {$_.matches}|Select-Object -Expand value -EA 0
                        If ($Null -eq $SVer) {
                            Write-Verbose "Unable to test version response from $($Svr)."
                            Continue
                        }

                        If (($PSCmdlet.ParameterSetName -eq 'installertoken')) {
                            $installer = "$($Svr)/LabTech/Deployment.aspx?InstallerToken=$InstallerToken"
                            If ([System.Version]$SVer -ge [System.Version]'240.331') {
                                Write-Debug "Line $(LINENUM): New MSI Installer Format Needed"
                                $InstallMSI='Agent_Install.zip'
                            }
                        } ElseIf ($ServerPassword) {
                            $installer = "$($Svr)/LabTech/Service/LabTechRemoteAgent.msi"
                        } ElseIf ([System.Version]$SVer -ge [System.Version]'110.374') {
                            #New Style Download Link starting with LT11 Patch 13 - Direct Location Targeting is no longer available
                            $installer = "$($Svr)/LabTech/Deployment.aspx?Probe=1&installType=msi&MSILocations=1"
                        } Else {
                            #Original URL
                            Write-Warning 'Update your damn server!'
                            $installer = "$($Svr)/LabTech/Deployment.aspx?Probe=1&installType=msi&MSILocations=$LocationID"
                        }#End If

                        # Vuln test June 10, 2020: ConnectWise Automate API Vulnerability - Only test if version is below known minimum.
                        If ([System.Version]$SVer -lt [System.Version]'200.197') {
                            Try{
                                $HTTP_Request = [System.Net.WebRequest]::Create("$($Svr)/LabTech/Deployment.aspx")
                                If ($HTTP_Request.GetResponse().StatusCode -eq 'OK') {
                                    $Message = @('Your server is vulnerable!!')
                                    $Message += 'https://docs.connectwise.com/ConnectWise_Automate/ConnectWise_Automate_Supportability_Statements/Supportability_Statement%3A_ConnectWise_Automate_Mitigation_Steps'
                                    Write-Warning $($Message | Out-String)
                                }
                            } Catch {
                                If (!$ServerPassword) {
                                    Write-Error 'Anonymous downloads are not allowed. ServerPassword or InstallerToken may be needed.'
                                    Continue
                                }
                            }
                        }#End If

                        If ( $PSCmdlet.ShouldProcess($installer, "DownloadFile") ) {
                            Write-Debug "Line $(LINENUM): Downloading $InstallMSI from $installer"
                            $Script:LTServiceNetWebClient.DownloadFile($installer,"$InstallBase\Installer\$InstallMSI")
                            If((Test-Path "$InstallBase\Installer\$InstallMSI") -and  !((Get-Item "$InstallBase\Installer\$InstallMSI" -EA 0).length/1KB -gt 1234)) {
                                Write-Warning "WARNING: Line $(LINENUM): $InstallMSI size is below normal. Removing suspected corrupt file."
                                Remove-Item "$InstallBase\Installer\$InstallMSI" -ErrorAction SilentlyContinue -Force -Confirm:$False
                                Continue
                            }#End If
                        }#End If

                        If ($WhatIfPreference -eq $True) {
                            $GoodServer = $Svr
                        } ElseIf (Test-Path "$InstallBase\Installer\$InstallMSI") {
                            $GoodServer = $Svr
                            Write-Verbose "$InstallMSI downloaded successfully from server $($Svr)."
                            If (($PSCmdlet.ParameterSetName -eq 'installertoken') -and [System.Version]$SVer -ge [System.Version]'240.331') {
                                Expand-Archive "$InstallBase\Installer\$InstallMSI" -DestinationPath "$InstallBase\Installer" -Force
                                #Cleanup .ZIP
                                Remove-Item "$InstallBase\Installer\$InstallMSI" -ErrorAction SilentlyContinue -Force -Confirm:$False
                                #Reset InstallMSI Value
                                $InstallMSI='Agent_Install.msi'
                            }#End If
                        } Else {
                            Write-Warning "WARNING: Line $(LINENUM): Error encountered downloading from $($Svr). No installation file was received."
                            Continue
                        }#End If
                    }#End Try
                    Catch {
                        Write-Warning "WARNING: Line $(LINENUM): Error encountered downloading from $($Svr)."
                        Continue
                    }
                } Else {
                    Write-Warning "WARNING: Line $(LINENUM): Server address $($Svr) is not formatted correctly. Example: https://lt.domain.com"
                }
            } Else {
                Write-Debug "Line $(LINENUM): Server $($GoodServer) has been selected."
                Write-Verbose "Server has already been selected - Skipping $($Svr)."
            }
        }#End Foreach
    }#End Process

    End{
        If ($GoodServer) {

            If ( $WhatIfPreference -eq $True -and (Get-PSCallStack)[1].Command -eq 'Redo-LTService' ) {
                Write-Debug "Line $(LINENUM): Skipping Preinstall Check: Called by Redo-LTService and ""-WhatIf=`$True"""
            } Else {
                If ((Test-Path "${env:windir}\ltsvc" -EA 0) -or (Test-Path "${env:windir}\temp\_ltupdate" -EA 0) -or (Test-Path registry::HKLM\Software\LabTech\Service -EA 0) -or (Test-Path registry::HKLM\Software\WOW6432Node\Labtech\Service -EA 0)){
                    Write-Warning "WARNING: Line $(LINENUM): Previous installation detected. Calling Uninstall-LTService"
                    Uninstall-LTService -Server $GoodServer -Force
                    Start-Sleep 10
                }#End If
            }#End If

            If ($WhatIfPreference -ne $True) {
                $GoodTrayPort=$Null;
                $TestTrayPort=$TrayPort;
                For ($i=0; $i -le 10; $i++) {
                    If (-not ($GoodTrayPort)) {
                        If (-not (Test-LTPorts -TrayPort $TestTrayPort -Quiet)){
                            $TestTrayPort++;
                            If ($TestTrayPort -gt 42009) {$TestTrayPort=42000}
                        } Else {
                            $GoodTrayPort=$TestTrayPort
                        }#End If
                    }#End If
                }#End For
                If ($GoodTrayPort -and $GoodTrayPort -ne $TrayPort -and $GoodTrayPort -ge 1 -and $GoodTrayPort -le 65535) {
                    Write-Verbose "TrayPort $($TrayPort) is in use. Changing TrayPort to $($GoodTrayPort)"
                    $TrayPort=$GoodTrayPort
                }#End If
                Write-Output "Starting Install."
            }#End If

            #Build parameter string
            $iarg =($(
                "/i `"$InstallBase\Installer\$InstallMSI`""
                "SERVERADDRESS=$GoodServer"
                If (($PSCmdlet.ParameterSetName -eq 'installertoken') -and [System.Version]$SVer -ge [System.Version]'240.331') {"TRANSFORMS=`"Agent_Install.mst`""}
                If ($ServerPassword -and $ServerPassword -match '.') {"SERVERPASS=`"$($ServerPassword)`""}
                If ($LocationID -and $LocationID -match '^\d+$') {"LOCATION=$LocationID"}
                If ($TrayPort -and $TrayPort -ne 42000) {"SERVICEPORT=$TrayPort"}
                "/qn"
                "/l `"$InstallBase\$logfile.log`""
                ) | Where-Object {$_}) -join ' '

            Try{
                If ( $PSCmdlet.ShouldProcess("msiexec.exe $($iarg)", "Execute Install") ) {
                    $InstallAttempt=0
                    Do {
                        If ($InstallAttempt -gt 0 ) {
                            Write-Warning "WARNING: Line $(LINENUM): Service Failed to Install. Retrying in 30 seconds." -WarningAction 'Continue'
                            $timeout = new-timespan -Seconds 30
                            $sw = [diagnostics.stopwatch]::StartNew()
                            Do {
                                Start-Sleep 5
                                $svcRun = ('LTService') | Get-Service -EA 0 | Measure-Object | Select-Object -Expand Count
                            } Until ($sw.elapsed -gt $timeout -or $svcRun -eq 1)
                            $sw.Stop()
                        }#End If
                        $InstallAttempt++
                        $svcRun = ('LTService') | Get-Service -EA 0 | Measure-Object | Select-Object -Expand Count
                        If ($svcRun -eq 0) {
                            Write-Verbose "Launching Installation Process: msiexec.exe $(($iarg -join ''))"
                            Start-Process -Wait -FilePath "${env:windir}\system32\msiexec.exe" -ArgumentList $iarg -WorkingDirectory $env:TEMP
                            Start-Sleep 5
                        }
                        $svcRun = ('LTService') | Get-Service -EA 0 | Measure-Object | Select-Object -Expand Count
                    } Until ($InstallAttempt -ge 3 -or $svcRun -eq 1)
                    If ($svcRun -eq 0) {
                        Write-Error "ERROR: Line $(LINENUM): LTService was not installed. Installation failed."
                        Return
                    }
                }#End If
                If (($Script:LTProxy.Enabled) -eq $True) {
                    Write-Verbose "Proxy Configuration Needed. Applying Proxy Settings to Agent Installation."
                    If ( $PSCmdlet.ShouldProcess($Script:LTProxy.ProxyServerURL, "Configure Agent Proxy") ) {
                        $svcRun = ('LTService') | Get-Service -EA 0 | Where-Object {$_.Status -eq 'Running'} | Measure-Object | Select-Object -Expand Count
                        If ($svcRun -ne 0) {
                            $timeout = new-timespan -Minutes 2
                            $sw = [diagnostics.stopwatch]::StartNew()
                            Write-Host -NoNewline "Waiting for Service to Start."
                            Do {
                                Write-Host -NoNewline '.'
                                Start-Sleep 2
                                $svcRun = ('LTService') | Get-Service -EA 0 | Where-Object {$_.Status -eq 'Running'} | Measure-Object | Select-Object -Expand Count
                            } Until ($sw.elapsed -gt $timeout -or $svcRun -eq 1)
                            Write-Host ""
                            $sw.Stop()
                            If ($svcRun -eq 1) {
                                Write-Debug "Line $(LINENUM): LTService Initial Startup Successful."
                            } Else {
                                Write-Debug "Line $(LINENUM): LTService Initial Startup failed to complete within expected period."
                            }#End If
                        }#End If
                        Set-LTProxy -ProxyServerURL $Script:LTProxy.ProxyServerURL -ProxyUsername $Script:LTProxy.ProxyUsername -ProxyPassword $Script:LTProxy.ProxyPassword -Confirm:$False -WhatIf:$False
                    }#End If
                } Else {
                    Write-Verbose "No Proxy Configuration has been specified - Continuing."
                }#End If
                If (!($NoWait) -and $PSCmdlet.ShouldProcess("LTService","Monitor For Successful Agent Registration") ) {
                    $timeout = new-timespan -Minutes 3
                    $sw = [diagnostics.stopwatch]::StartNew()
                    Write-Host -NoNewline "Waiting for agent to register."
                    Do {
                        Write-Host -NoNewline '.'
                        Start-Sleep 5
                        $tmpLTSI = (Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand 'ID' -EA 0)
                    } Until ($sw.elapsed -gt $timeout -or $tmpLTSI -ge 1)
                    Write-Host ""
                    $sw.Stop()
                    Write-Verbose "Completed wait for LabTech Installation after $(([int32]$sw.Elapsed.TotalSeconds).ToString()) seconds."
                    $Null=Get-LTProxy -ErrorAction Continue
                }#End If
                If ($Hide) {Hide-LTAddRemove}
            }#End Try

            Catch{
                Write-Error "ERROR: Line $(LINENUM): There was an error during the install process. $($Error[0])"
                Return
            }#End Catch

            If ($WhatIfPreference -ne $True) {
                #Cleanup Install files
                Remove-Item "$InstallBase\Installer\$InstallMSI" -ErrorAction SilentlyContinue -Force -Confirm:$False
                Remove-Item "$InstallBase\Installer\Agent_Install.mst" -ErrorAction SilentlyContinue -Force -Confirm:$False
                @($curlog,"${env:windir}\LTSvc\Install.log") | Foreach-Object {
                    If ((Test-Path -PathType Leaf -LiteralPath $($_))) {
                        $logcontents=Get-Content -Path $_
                        $logcontents=$logcontents -replace '(?<=PreInstallPass:[^\r\n]+? (?:result|value)): [^\r\n]+',': <REDACTED>'
                        If ($logcontents) {Set-Content -Path $_ -Value $logcontents -Force -Confirm:$False}
                    }#End If
                }#End Foreach-Object

                $tmpLTSI = Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False
                If (($tmpLTSI)) {
                    If (($tmpLTSI|Select-Object -Expand 'ID' -EA 0) -ge 1) {
                        Write-Output "LabTech has been installed successfully. Agent ID: $($tmpLTSI|Select-Object -Expand 'ID' -EA 0) LocationID: $($tmpLTSI|Select-Object -Expand 'LocationID' -EA 0)"
                    } ElseIf (!($NoWait)) {
                        Write-Error "ERROR: Line $(LINENUM): LabTech installation completed but Agent failed to register within expected period." -ErrorAction Continue
                    } Else {
                        Write-Warning "WARNING: Line $(LINENUM): LabTech installation completed but Agent did not yet register." -WarningAction Continue
                    }#End If
                } Else {
                    If (($Error)) {
                        Write-Error "ERROR: Line $(LINENUM): There was an error installing LabTech. Check the log, $InstallBase\$logfile.log $($Error[0])"
                        Return
                    } ElseIf (!($NoWait)) {
                        Write-Error "ERROR: Line $(LINENUM): There was an error installing LabTech. Check the log, $InstallBase\$logfile.log"
                        Return
                    } Else {
                        Write-Warning "WARNING: Line $(LINENUM): LabTech installation may not have succeeded." -WarningAction Continue
                    }#End If
                }#End If
            }#End If
            If (($Rename) -and $Rename -notmatch 'False'){ Rename-LTAddRemove -Name $Rename }
        } ElseIf ( $WhatIfPreference -ne $True ) {
            Write-Error "ERROR: Line $(LINENUM): No valid server was reached to use for the install."
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Install-LTService

Function Redo-LTService{
<#
.SYNOPSIS
    This function will reinstall the LabTech agent from the machine.

.DESCRIPTION
    This script will attempt to pull all current settings from machine and issue an 'Uninstall-LTService', 'Install-LTService' with gathered information.
    If the function is unable to find the settings it will ask for needed parameters.

.PARAMETER Server
    This is the URL to your LabTech server.
    Example: https://lt.domain.com
    This is used to download the installation and removal utilities.
    If no server is provided the uninstaller will use Get-LTServiceInfo to get the server address.
    If it is unable to find LT currently installed it will try Get-LTServiceInfoBackup

.PARAMETER ServerPassword
    This is the Server Password to your LabTech server.
    SELECT SystemPassword FROM config;

.PARAMETER InstallerToken
    Permits use of installer tokens for customized MSI downloads. (Other installer types are not supported)

.PARAMETER LocationID
    The LocationID of the location that you want the agent in
    example: 555

.PARAMETER Backup
    This will run a New-LTServiceBackup command before uninstalling.

.PARAMETER Hide
    Will remove from add-remove programs

.PARAMETER Rename
    This will call Rename-LTAddRemove to rename the install in Add/Remove Programs

.PARAMETER SkipDotNet
    This will disable the error checking for the .NET 3.5 and .NET 2.0 frameworks during the install process.

.PARAMETER Force
    This will force operation on an agent detected as a probe.

.EXAMPLE
    Redo-LTService
    This will ReInstall the LabTech agent using the server address in the registry.

.EXAMPLE
    Redo-LTService -Server https://lt.domain.com -Password sQWZzEDYKFFnTT0yP56vgA== -LocationID 42
    This will ReInstall the LabTech agent using the provided server URL to download the installation files.

.NOTES
    Version:        1.6
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 6/8/2017
    Purpose/Change: Update to support user provided settings for -Server, -Password, -LocationID.

    Update Date: 6/10/2017
    Purpose/Change: Updates for pipeline input, support for multiple servers

    Update Date: 8/24/2017
    Purpose/Change: Update to use Clear-Variable.

    Update Date: 3/12/2018
    Purpose/Change: Added detection of "Probe" enabled agent.
    Added support for -Force parameter to override probe detection.
    Updated support of -WhatIf parameter.

    Update Date: 2/22/2019
    Purpose/Change: Added -SkipDotNet parameter.
    Allows for skipping of .NET 3.5 and 2.0 framework checks for installing on OS with .NET 4.0+ already installed

    Update Date: 6/20/2020
    Purpose/Change: Added -InstallerToken parameter.
    Allows for MSI download and installation without -ServerPassword parameter

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName = 'deployment')]
    Param(
        [Parameter(ParameterSetName = 'deployment')]
        [Parameter(ParameterSetName = 'installertoken')]
        [Parameter(ValueFromPipelineByPropertyName = $True, ValueFromPipeline=$True)]
        [AllowNull()]
        [string[]]$Server,
        [Parameter(ParameterSetName = 'deployment')]
        [Parameter(ValueFromPipelineByPropertyName = $True, ValueFromPipeline=$True)]
        [Alias("Password")]
        [string]$ServerPassword,
        [Parameter(ParameterSetName = 'installertoken')]
        [ValidatePattern('(?s:^[0-9a-z]+$)')]
        [string]$InstallerToken,
        [Parameter(ValueFromPipelineByPropertyName = $True)]
        [AllowNull()]
        [string]$LocationID,
        [switch]$Backup,
        [switch]$Hide,
        [Parameter()]
        [AllowNull()]
        [string]$Rename,
        [switch]$SkipDotNet,
        [switch]$Force
    )

    Begin{
        Clear-Variable PasswordArg, RenameArg, Svr, ServerList, Settings -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"

        # Gather install stats from registry or backed up settings
        Try {
            $Settings = Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False
            If ($Null -ne $Settings) {
                If (($Settings|Select-Object -Expand Probe -EA 0) -eq '1') {
                    If ($Force -eq $True) {
                        Write-Output "Probe Agent Detected. Re-Install Forced."
                    } Else {
                        If ($WhatIfPreference -ne $True) {
                            Write-Error -Exception [System.OperationCanceledException]"ERROR: Line $(LINENUM): Probe Agent Detected. Re-Install Denied." -ErrorAction Stop
                        } Else {
                            Write-Error -Exception [System.OperationCanceledException]"What If: Line $(LINENUM): Probe Agent Detected. Re-Install Denied." -ErrorAction Stop
                        }#End If
                    }#End If
                }#End If
            }#End If
        } Catch {
            Write-Debug "Line $(LINENUM): Failed to retrieve current Agent Settings."
        }#End Catch
        If ($Null -eq $Settings) {
            Write-Debug "Line $(LINENUM): Unable to retrieve current Agent Settings. Testing for Backup Settings"
            Try {
                $Settings = Get-LTServiceInfoBackup -EA 0
            } Catch {}
        }
        $ServerList=@()
    }#End Begin

    Process{
        if (-not ($Server)){
            if ($Settings){
                $Server = $Settings|Select-Object -Expand 'Server' -EA 0
            }
            if (-not ($Server)){
                $Server = Read-Host -Prompt 'Provide the URL to your LabTech server (https://lt.domain.com):'
            }
        }
        if (-not ($LocationID)){
            if ($Settings){
                $LocationID = $Settings|Select-Object -Expand LocationID -EA 0
            }
            if (-not ($LocationID)){
                $LocationID = Read-Host -Prompt 'Provide the LocationID'
            }
        }
        if (-not ($LocationID)){
            $LocationID = "1"
        }
        $ServerList += $Server
    }#End Process

    End{
        If ($Backup){
            If ( $PSCmdlet.ShouldProcess("LTService","Backup Current Service Settings") ) {
                New-LTServiceBackup
            }#End If
        }#End If

        $RenameArg=''
        If ($Rename){
            $RenameArg = "-Rename $Rename"
        }

        If ($PSCmdlet.ParameterSetName -eq 'installertoken') {
            $PasswordPresent = "-InstallerToken 'REDACTED'"
        } ElseIf (($ServerPassword)){
            $PasswordPresent = "-Password 'REDACTED'"
        }

        Write-Output "Reinstalling LabTech with the following information, -Server $($ServerList -join ',') $PasswordPresent -LocationID $LocationID $RenameArg"
        Write-Verbose "Starting: Uninstall-LTService -Server $($ServerList -join ',')"
        Try{
            Uninstall-LTService -Server $ServerList -ErrorAction Stop -Force
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error during the reinstall process while uninstalling. $($Error[0])" -ErrorAction Stop
        }#End Catch

        Finally{
            If ($WhatIfPreference -ne $True) {
                Write-Verbose "Waiting 20 seconds for prior uninstall to settle before starting Install."
                Start-Sleep 20
            }
        }

        Write-Verbose "Starting: Install-LTService -Server $($ServerList -join ',') $PasswordPresent -LocationID $LocationID -Hide:`$$($Hide) $RenameArg"
        Try{
            If ($PSCmdlet.ParameterSetName -ne 'installertoken') {
                Install-LTService -Server $ServerList -ServerPassword $ServerPassword -LocationID $LocationID -Hide:$Hide -Rename $Rename -SkipDotNet:$SkipDotNet -Force
            } Else {
                Install-LTService -Server $ServerList -InstallerToken $InstallerToken -LocationID $LocationID -Hide:$Hide -Rename $Rename -SkipDotNet:$SkipDotNet -Force
            }
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error during the reinstall process while installing. $($Error[0])" -ErrorAction Stop
        }#End Catch

        If (!($?)){
            $($Error[0])
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Redo-LTService
Set-Alias -Name ReInstall-LTService -Value Redo-LTService

Function Update-LTService{
<#
.SYNOPSIS
    This function will manually update the LabTech agent to the requested version.

.DESCRIPTION
    This script will attempt to pull current server settings from machine, then download and run the agent updater.


.PARAMETER Version
    This is the agent version to install.
    Example: 120.240
    This is needed to download the update file. If omitted, the version advertised by the server will be used.

.EXAMPLE
    Update-LTService -Version 120.240
    This will update the Automate agent to the specific version requested, using the server address in the registry.

.EXAMPLE
    Update-LTService
    This will update the Automate agent to the current version advertised, using the server address in the registry.

.NOTES
    Version:        1.1
    Author:         Darren White
    Creation Date:  8/28/2018
    Purpose/Change: Initial function development

    Update Date: 1/21/2019
    Purpose/Change: Minor bugfixes/adjustments.
    Allow single label server name, accept less digits for Agent Minor version number

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param(
        [parameter(Position=0)]
        [AllowNull()]
        [string]$Version
    )

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
        Clear-Variable Svr, GoodServer, Settings -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        $Settings = Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False
        $updaterPath = [System.Environment]::ExpandEnvironmentVariables("%windir%\temp\_LTUpdate")
        $xarg=@("/o""$updaterPath""","/y")
        $uarg=@("""$updaterPath\Update.ini""")
    }#End Begin

    Process{
        if (-not ($Server)){
            If ($Settings){
                $Server = $Settings|Select-Object -Expand 'Server' -EA 0
            }
        }

        $Server=ForEach ($Svr in $Server) {If ($Svr -notmatch 'https?://.+') {"https://$($Svr)"}; $Svr}
        Foreach ($Svr in $Server) {
            If (-not ($GoodServer)) {
                If ($Svr -match '^(https?://)?(([12]?[0-9]{1,2}\.){3}[12]?[0-9]{1,2}|[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*)$') {
                    If ($Svr -notmatch 'https?://.+') {$Svr = "http://$($Svr)"}
                    Try {
                        $SvrVerCheck = "$($Svr)/LabTech/Agent.aspx"
                        Write-Debug "Line $(LINENUM): Testing Server Response and Version: $SvrVerCheck"
                        $SvrVer = $Script:LTServiceNetWebClient.DownloadString($SvrVerCheck)
                        Write-Debug "Line $(LINENUM): Raw Response: $SvrVer"
                        $SVer = $SvrVer|select-string -pattern '(?<=[|]{6})[0-9]{1,3}\.[0-9]{1,3}'|ForEach-Object {$_.matches}|Select-Object -Expand value -EA 0
                        If ($Null -eq ($SVer)) {
                            Write-Verbose "Unable to test version response from $($Svr)."
                            Continue
                        }
                        If ($Version -match '[1-9][0-9]{2}\.[0-9]{1,3}') {
                            $updater = "$($Svr)/LabTech/Updates/LabtechUpdate_$($Version).zip"
                        } ElseIf ([System.Version]$SVer -ge [System.Version]'105.001') {
                            $Version = $SVer
                            Write-Verbose "Using detected version ($Version) from server: $($Svr)."
                            $updater = "$($Svr)/LabTech/Updates/LabtechUpdate_$($Version).zip"
                        }

                        #Kill all running processes from $updaterPath
                        if (Test-Path $updaterPath){
                            $Executables = (Get-ChildItem $updaterPath -Filter *.exe -Recurse -ErrorAction SilentlyContinue|Select-Object -Expand FullName)
                            if ($Executables) {
                                Write-Verbose "Terminating LabTech Processes from $($updaterPath) if found running: $(($Executables) -replace [Regex]::Escape($updaterPath),'' -replace '^\\','')"
                                Get-Process | Where-Object {$Executables -contains $_.Path } | ForEach-Object {
                                    Write-Debug "Line $(LINENUM): Terminating Process $($_.ProcessName)"
                                    $($_) | Stop-Process -Force -ErrorAction SilentlyContinue
                                }
                            }
                        }#End If

                        #Remove $updaterPath - Depth First Removal, First by purging files, then Removing Folders, to get as much removed as possible if complete removal fails
                        @("$updaterPath") | foreach-object {
                            If ((Test-Path "$($_)" -EA 0)) {
                                If ( $PSCmdlet.ShouldProcess("$($_)","Remove Folder") ) {
                                    Write-Debug "Line $(LINENUM): Removing Folder: $($_)"
                                    Try {
                                        Get-ChildItem -Path $_ -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { ($_.psiscontainer) } | foreach-object { Get-ChildItem -Path "$($_.FullName)" -EA 0 | Where-Object { -not ($_.psiscontainer) } | Remove-Item -Force -ErrorAction SilentlyContinue -Confirm:$False -WhatIf:$False }
                                        Get-ChildItem -Path $_ -Recurse -Force -ErrorAction SilentlyContinue | Where-Object { ($_.psiscontainer) } | Sort-Object { $_.fullname.length } -Descending | Remove-Item -Force -ErrorAction SilentlyContinue -Recurse -Confirm:$False -WhatIf:$False
                                        Remove-Item -Recurse -Force -Path $_ -ErrorAction SilentlyContinue -Confirm:$False -WhatIf:$False
                                    } Catch {}
                                }#End If
                            }#End If
                        }#End Foreach-Object

                        Try {
                            If (-not (Test-Path -PathType Container -Path "$updaterPath" )){
                                New-Item "$updaterPath" -type directory -ErrorAction SilentlyContinue | Out-Null
                            }#End if
                            $updaterTest = [System.Net.WebRequest]::Create($updater)
                            If (($Script:LTProxy.Enabled) -eq $True) {
                                Write-Debug "Line $(LINENUM): Proxy Configuration Needed. Applying Proxy Settings to request."
                                $updaterTest.Proxy=$Script:LTWebProxy
                            }#End If
                            $updaterTest.KeepAlive=$False
                            $updaterTest.ProtocolVersion = '1.0'
                            $updaterResult = $updaterTest.GetResponse()
                            $updaterTest.Abort()
                            If ($updaterResult.StatusCode -ne 200) {
                                Write-Warning "WARNING: Line $(LINENUM): Unable to download LabtechUpdate.exe version $Version from server $($Svr)."
                                Continue
                            } Else {
                                If ( $PSCmdlet.ShouldProcess($updater, "DownloadFile") ) {
                                    Write-Debug "Line $(LINENUM): Downloading LabtechUpdate.exe from $updater"
                                    $Script:LTServiceNetWebClient.DownloadFile($updater,"$updaterPath\LabtechUpdate.exe")
                                    If((Test-Path "$updaterPath\LabtechUpdate.exe") -and  !((Get-Item "$updaterPath\LabtechUpdate.exe" -EA 0).length/1KB -gt 1234)) {
                                        Write-Warning "WARNING: Line $(LINENUM): LabtechUpdate.exe size is below normal. Removing suspected corrupt file."
                                        Remove-Item "$updaterPath\LabtechUpdate.exe" -ErrorAction SilentlyContinue -Force -Confirm:$False
                                        Continue
                                    }#End If
                                }#End If

                                If ($WhatIfPreference -eq $True) {
                                    $GoodServer = $Svr
                                } ElseIf (Test-Path "$updaterPath\LabtechUpdate.exe") {
                                    $GoodServer = $Svr
                                    Write-Verbose "LabtechUpdate.exe downloaded successfully from server $($Svr)."
                                } Else {
                                    Write-Warning "WARNING: Line $(LINENUM): Error encountered downloading from $($Svr). No update file was received."
                                    Continue
                                }#End If
                            }#End If
                        }#End Try
                        Catch {
                            Write-Warning "WARNING: Line $(LINENUM): Error encountered downloading $updater."
                            Continue
                        }
                    }#End Try
                    Catch {
                        Write-Warning "WARNING: Line $(LINENUM): Error encountered downloading from $($Svr)."
                        Continue
                    }
                } Else {
                    Write-Warning "WARNING: Line $(LINENUM): Server address $($Svr) is not formatted correctly. Example: https://lt.domain.com"
                }
            } Else {
                Write-Debug "Line $(LINENUM): Server $($GoodServer) has been selected."
                Write-Verbose "Server has already been selected - Skipping $($Svr)."
            }
        }#End Foreach
    }#End Process

    End{
        $detectedVersion = $Settings|Select-Object -Expand 'Version' -EA 0
        If ($Null -eq $detectedVersion){
            Write-Error "ERROR: Line $(LINENUM): No existing installation was found." -ErrorAction Stop
            Return
        }
        If ([System.Version]$detectedVersion -ge [System.Version]$Version) {
            Write-Warning "WARNING: Line $(LINENUM): Installed version detected ($detectedVersion) is higher than or equal to the requested version ($Version)."
            Return
        }
        If (-not ($GoodServer)) {
            Write-Warning "WARNING: Line $(LINENUM): No valid server was detected."
            Return
        }
        If ([System.Version]$SVer -gt [System.Version]$Version) {
            Write-Warning "WARNING: Line $(LINENUM): Server version detected ($SVer) is higher than the requested version ($Version)."
            Return
        }

        Try{
            Stop-LTService
        }#End Try
        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error stopping the services. $($Error[0])"
            Return
        }#End Catch

        Write-Output "Updating Agent with the following information: Server $($GoodServer), Version $Version"
        Try{
            If ($PSCmdlet.ShouldProcess("LabtechUpdate.exe $($xarg)", "Extracting update files")) {
                If ((Test-Path "$updaterPath\LabtechUpdate.exe")) {
                    #Extract Update Files
                    Write-Verbose "Launching LabtechUpdate Self-Extractor."
                    Write-Debug "Line $(LINENUM): Executing Command ""LabtechUpdate.exe $($xarg)"""
                    Try {
                        Push-Location $updaterPath
                        & "$updaterPath\LabtechUpdate.exe" $($xarg) 2>''
                        Pop-Location
                    }
                    Catch {Write-Output "Error calling LabtechUpdate.exe."}
                    Start-Sleep -Seconds 5
                } Else {
                    Write-Verbose "WARNING: $updaterPath\LabtechUpdate.exe was not found."
                }
            }#End If

            If ($PSCmdlet.ShouldProcess("Update.exe $($uarg)", "Launching Updater")) {
                If ((Test-Path "$updaterPath\Update.exe")) {
                    #Extract Update Files
                    Write-Verbose "Launching Labtech Updater"
                    Write-Debug "Line $(LINENUM): Executing Command ""Update.exe $($uarg)"""
                    Try {& "$updaterPath\Update.exe" $($uarg) 2>''}
                    Catch {Write-Output "Error calling Update.exe."}
                    Start-Sleep -Seconds 5
                } Else {
                    Write-Verbose "WARNING: $updaterPath\Update.exe was not found."
                }
            }#End If

        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error during the update process $($Error[0])" -ErrorAction Continue
        }#End Catch

        Try{
            Start-LTService
        }#End Try
        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error starting the services. $($Error[0])"
            Return
        }#End Catch

        If ($WhatIfPreference -ne $True) {
            If ($?) {}
            Else {$Error[0]}
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Update-LTService

Function Get-LTErrors{
<#
.SYNOPSIS
    This will pull the %ltsvcdir%\LTErrors.txt file into an object.

.EXAMPLE
    Get-LTErrors | where {(Get-date $_.Time) -gt (get-date).AddHours(-24)}
    Get a list of all errors in the last 24hr

.EXAMPLE
    Get-LTErrors | Out-Gridview
    Open the log file in a sortable searchable window.

.NOTES
    Version:        1.3
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/18/2018
    Purpose/Change: Changed Erroraction from Stop to unspecified to allow caller to set the ErrorAction.

    Update Date: 1/26/2019
    Purpose/Change: Update for better international date parsing support. Function rename.

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param()

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
        $BasePath = $(Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand BasePath -EA 0)
        if (!$BasePath){$BasePath = "${env:windir}\LTSVC"}
    }#End Begin

    Process{
        if ($(Test-Path -Path "$BasePath\LTErrors.txt") -eq $False) {
            Write-Error "ERROR: Line $(LINENUM): Unable to find lelog."
            return
        }
        Try{
            $errors = Get-Content "$BasePath\LTErrors.txt"
            $errors = $errors -join ' ' -split '::: '
            foreach($Line in $Errors){
                $items = $Line -split "`t" -replace ' - ',''
                if ($items[1]){
                    $object = New-Object -TypeName PSObject
                    $object | Add-Member -MemberType NoteProperty -Name ServiceVersion -Value $items[0]
                    $object | Add-Member -MemberType NoteProperty -Name Timestamp -Value $(Try {[datetime]::Parse($items[1])} Catch {})
                    $object | Add-Member -MemberType NoteProperty -Name Message -Value $items[2]
                    Write-Output $object
                }
            }

        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error reading the log. $($Error[0])"
        }#End Catch
    }#End Process

    End{
        if ($?){
        }
        Else {$Error[0]}
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Get-LTErrors
Set-Alias -Name Get-LTError -Value Get-LTErrors

Function Reset-LTService{
<#
.SYNOPSIS
    This function will remove local settings on the agent.

.DESCRIPTION
    This function can remove some of the agents local settings.
    ID, MAC, LocationID
    The function will stop the services, make the change, then start the services.
    Resetting all of these will force the agent to check in as a new agent.
    If you have MAC filtering enabled it should check back in with the same ID.
    This function is useful for duplicate agents.

.PARAMETER ID
    This will reset the AgentID of the computer

.PARAMETER Location
    This will reset the LocationID of the computer

.PARAMETER MAC
    This will reset the MAC of the computer

.PARAMETER Force
    This will force operation on an agent detected as a probe.

.PARAMETER NoWait
    This will skip the ending health check for the reset process.
    The function will exit once the values specified have been reset.

.EXAMPLE
    Reset-LTService
    This resets the ID, MAC and LocationID on the agent.

.EXAMPLE
    Reset-LTService -ID
    This resets only the ID of the agent.

.NOTES
    Version:        1.4
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/12/2018
    Purpose/Change: Added detection of "Probe" enabled agent.
    Added support for -Force parameter to override probe detection. Added support for -WhatIf.
    Added support for -NoWait paramter to bypass agent health check.

    Update Date: 3/21/2018
    Purpose/Change: Removed ErrorAction Override

    Update Date: 8/5/2019
    Purpose/Change: Bugfixes for -Location parameter

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param(
        [switch]$ID,
        [switch]$Location,
        [switch]$MAC,
        [switch]$Force,
        [switch]$NoWait
    )

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"

        $Reg = 'HKLM:\Software\LabTech\Service'
        If (!$PsBoundParameters.ContainsKey('ID') -and !$PsBoundParameters.ContainsKey('Location') -and !$PsBoundParameters.ContainsKey('MAC')){
            $ID=$True
            $Location=$True
            $MAC=$True
        }#End If

        $LTSI=Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False
        If (($LTSI) -and ($LTSI|Select-Object -Expand Probe -EA 0) -eq '1') {
            If ($Force -eq $True) {
                Write-Output "Probe Agent Detected. Reset Forced."
            } Else {
                If ($WhatIfPreference -ne $True) {
                    Write-Error -Exception [System.OperationCanceledException]"ERROR: Line $(LINENUM): Probe Agent Detected. Reset Denied." -ErrorAction Stop
                } Else {
                    Write-Error -Exception [System.OperationCanceledException]"What If: Line $(LINENUM): Probe Agent Detected. Reset Denied." -ErrorAction Stop
                }#End If
            }#End If
        }#End If
        Write-Output "OLD ID: $($LTSI|Select-Object -Expand ID -EA 0) LocationID: $($LTSI|Select-Object -Expand LocationID -EA 0) MAC: $($LTSI|Select-Object -Expand MAC -EA 0)"
        $LTSI=$Null
    }#End Begin

    Process{
        If (!(Get-Service 'LTService','LTSvcMon' -ErrorAction SilentlyContinue)) {
            If ($WhatIfPreference -ne $True) {
                Write-Error "ERROR: Line $(LINENUM): LabTech Services NOT Found $($Error[0])"
                return
            } Else {
                Write-Error "What If: Line $(LINENUM): Stopping: LabTech Services NOT Found"
                return
            }#End If
        }#End If

        Try{
            If ($ID -or $Location -or $MAC) {
                Stop-LTService
                If ($ID) {
                    Write-Output ".Removing ID"
                    Remove-ItemProperty -Name ID -Path $Reg -ErrorAction SilentlyContinue
                }#End If
                If ($Location) {
                    Write-Output ".Removing LocationID"
                    Remove-ItemProperty -Name LocationID -Path $Reg -ErrorAction SilentlyContinue
                }#End If
                If ($MAC) {
                    Write-Output ".Removing MAC"
                    Remove-ItemProperty -Name MAC -Path $Reg -ErrorAction SilentlyContinue
                }#End If
                Start-LTService
            }
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error during the reset process. $($Error[0])" -ErrorAction Stop
        }#End Catch
    }#End Process

    End{
        If ($?){
            If (-NOT $NoWait -and $PSCmdlet.ShouldProcess("LTService", "Discover new settings after Service Start")) {
                $timeout = New-Timespan -Minutes 1
                $sw = [diagnostics.stopwatch]::StartNew()
                $LTSI=Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False
                Write-Host -NoNewline "Waiting for agent to register."
                While (!($LTSI|Select-Object -Expand ID -EA 0) -or !($LTSI|Select-Object -Expand LocationID -EA 0) -or !($LTSI|Select-Object -Expand MAC -EA 0) -and $($sw.elapsed) -lt $timeout){
                    Write-Host -NoNewline '.'
                    Start-Sleep 2
                    $LTSI=Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False
                }#End While
                Write-Host ""
                $LTSI=Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False
                Write-Output "NEW ID: $($LTSI|Select-Object -Expand ID -EA 0) LocationID: $($LTSI|Select-Object -Expand LocationID -EA 0) MAC: $($LTSI|Select-Object -Expand MAC -EA 0)"
            }#End If
        } Else {$Error[0]}
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Reset-LTService

Function Hide-LTAddRemove{
<#
.SYNOPSIS
    This function hides the LabTech install from the Add/Remove Programs list.

.DESCRIPTION
    This function will rename the DisplayName registry key to hide it from the Add/Remove Programs list.

.NOTES
    Version:        1.2
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/12/2018
    Purpose/Change: Support for ShouldProcess. Added Registry Paths to be checked.
    Modified hiding method to be compatible with standard software controls.

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param()

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
        $RegRoots = ('HKLM:\SOFTWARE\Classes\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F',
        'HKLM:\SOFTWARE\Classes\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC')
        $PublisherRegRoots = ('HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{58A3001D-B675-4D67-A5A1-0FA9F08CF7CA}',
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{58A3001D-B675-4D67-A5A1-0FA9F08CF7CA}',
        'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{3F460D4C-D217-46B4-80B6-B5ED50BD7CF5}',
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{3F460D4C-D217-46B4-80B6-B5ED50BD7CF5}')
        $RegEntriesFound=0
        $RegEntriesChanged=0
    }#End Begin

    Process{

        Try{
            Foreach($RegRoot in $RegRoots){
                If (Test-Path $RegRoot){
                    If (Get-ItemProperty $RegRoot -Name HiddenProductName -ErrorAction SilentlyContinue) {
                        If (!(Get-ItemProperty $RegRoot -Name ProductName -ErrorAction SilentlyContinue)) {
                            Write-Verbose "LabTech found with HiddenProductName value."
                            Try{
                                Rename-ItemProperty $RegRoot -Name HiddenProductName -NewName ProductName
                            }#End Try
                            Catch{
                                Write-Error "ERROR: Line $(LINENUM): There was an error renaming the registry value. $($Error[0])" -ErrorAction Stop
                            }#End Catch
                        } Else {
                            Write-Verbose "LabTech found with unused HiddenProductName value."
                            Try{
                                Remove-ItemProperty $RegRoot -Name HiddenProductName -EA 0 -Confirm:$False -WhatIf:$False -Force
                            }#End Try
                            Catch{}#End Catch
                        }#End If
                    }#End If
                }#End If
            }#End Foreach

            Foreach($RegRoot in $PublisherRegRoots){
                If (Test-Path $RegRoot){
                    $RegKey=Get-Item $RegRoot -ErrorAction SilentlyContinue
                    If ($RegKey){
                        $RegEntriesFound++
                        If ($PSCmdlet.ShouldProcess("$($RegRoot)", "Set Registry Values to Hide $($RegKey.GetValue('DisplayName'))")){
                            $RegEntriesChanged++
                            @('SystemComponent') | ForEach-Object {
                                If (($RegKey.GetValue("$($_)")) -ne 1) {
                                    Write-Verbose "Setting $($RegRoot)\$($_)=1"
                                    Set-ItemProperty $RegRoot -Name "$($_)" -Value 1 -Type DWord -WhatIf:$False -Confirm:$False -Verbose:$False
                                }#End If
                            }#End ForEach-Object
                        }#End If
                    }#End If
                }#End If
            }#End Foreach
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error setting the registry values. $($Error[0])" -ErrorAction Stop
        }#End Catch

    }#End Process

    End{
        If ($WhatIfPreference -ne $True) {
            If ($?){
                If ($RegEntriesFound -gt 0 -and $RegEntriesChanged -eq $RegEntriesFound) {
                    Write-Output "LabTech is hidden from Add/Remove Programs."
                } Else {
                    Write-Warning "WARNING: Line $(LINENUM): LabTech may not be hidden from Add/Remove Programs."
                }#End If
            }#End If
            Else {$Error[0]}
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Hide-LTAddRemove

Function Show-LTAddRemove{
<#
.SYNOPSIS
    This function shows the LabTech install in the add/remove programs list.

.DESCRIPTION
    This function will rename the HiddenDisplayName registry key to show it in the add/remove programs list.
    If there is not HiddenDisplayName key the function will import a new entry.

.NOTES
    Version:        1.2
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/12/2018
    Purpose/Change: Support for ShouldProcess. Added Registry Paths to be checked.
    Modified hiding method to be compatible with standard software controls.

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param()

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
        $RegRoots = ('HKLM:\SOFTWARE\Classes\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F',
        'HKLM:\SOFTWARE\Classes\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC')
        $PublisherRegRoots = ('HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{58A3001D-B675-4D67-A5A1-0FA9F08CF7CA}',
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{58A3001D-B675-4D67-A5A1-0FA9F08CF7CA}',
        'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{3F460D4C-D217-46B4-80B6-B5ED50BD7CF5}',
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{3F460D4C-D217-46B4-80B6-B5ED50BD7CF5}')
        $RegEntriesFound=0
        $RegEntriesChanged=0
    }#End Begin

    Process{

        Try{
            Foreach($RegRoot in $RegRoots){
                If (Test-Path $RegRoot){
                    If (Get-ItemProperty $RegRoot -Name HiddenProductName -ErrorAction SilentlyContinue) {
                        If (!(Get-ItemProperty $RegRoot -Name ProductName -ErrorAction SilentlyContinue)) {
                            Write-Verbose "LabTech found with HiddenProductName value."
                            Try{
                                Rename-ItemProperty $RegRoot -Name HiddenProductName -NewName ProductName
                            }#End Try
                            Catch{
                                Write-Error "ERROR: Line $(LINENUM): There was an error renaming the registry value. $($Error[0])" -ErrorAction Stop
                            }#End Catch
                        } Else {
                            Write-Verbose "LabTech found with unused HiddenProductName value."
                            Try{
                                Remove-ItemProperty $RegRoot -Name HiddenProductName -EA 0 -Confirm:$False -WhatIf:$False -Force
                            }#End Try
                            Catch{}#End Catch
                        }#End If
                    }#End If
                }#End If
            }#End Foreach

            Foreach($RegRoot in $PublisherRegRoots){
                If (Test-Path $RegRoot){
                    $RegKey=Get-Item $RegRoot -ErrorAction SilentlyContinue
                    If ($RegKey){
                        $RegEntriesFound++
                        If ($PSCmdlet.ShouldProcess("$($RegRoot)", "Set Registry Values to Show $($RegKey.GetValue('DisplayName'))")){
                            $RegEntriesChanged++
                            @('SystemComponent') | ForEach-Object {
                                If (($RegKey.GetValue("$($_)")) -eq 1) {
                                    Write-Verbose "Setting $($RegRoot)\$($_)=0"
                                    Set-ItemProperty $RegRoot -Name "$($_)" -Value 0 -Type DWord -WhatIf:$False -Confirm:$False -Verbose:$False
                                }#End If
                            }#End ForEach-Object
                        }#End If
                    }#End If
                }#End If
            }#End Foreach
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error setting the registry values. $($Error[0])" -ErrorAction Stop
        }#End Catch

    }#End Process

    End{
        If ($WhatIfPreference -ne $True) {
            If ($?){
                If ($RegEntriesFound -gt 0 -and $RegEntriesChanged -eq $RegEntriesFound) {
                    Write-Output "LabTech is visible from Add/Remove Programs."
                } Else {
                    Write-Warning "WARNING: Line $(LINENUM): LabTech may not be visible from Add/Remove Programs."
                }#End If
            }#End If
            Else {$Error[0]}
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Show-LTAddRemove

Function Test-LTPorts{
<#
.SYNOPSIS
    This function will attempt to connect to all required TCP ports.

.DESCRIPTION
    The function will confirm the LTTray port is available locally.
    It will then test required TCP ports to the Server.

.PARAMETER Server
    This is the URL to your LabTech server.
    Example: https://lt.domain.com
    If no server is provided the function will use Get-LTServiceInfo to
    get the server address. If it is unable to find LT currently installed
    it will try calling Get-LTServiceInfoBackup.

.PARAMETER TrayPort
    This is the port LTSvc.exe listens on for communication with LTTray.
    It will be checked to verify it is available. If not provided the
    default port will be used (42000).

.PARAMETER Quiet
    This will return a boolean for connectivity status to the Server

.NOTES
    Version:        1.6
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date:    5/11/2017
    Purpose/Change: Quiet feature

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 6/10/2017
    Purpose/Change: Updates for pipeline input, support for multiple servers

    Update Date: 8/24/2017
    Purpose/Change: Update to use Clear-Variable.

    Update Date: 8/29/2017
    Purpose/Change: Added Server Address Format Check

    Update Date: 2/13/2018
    Purpose/Change: Added -TrayPort parameter.

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipelineByPropertyName = $true, ValueFromPipeline=$True)]
        [string[]]$Server,
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [int]$TrayPort,
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [switch]$Quiet
    )

    Begin{
        Function Private:TestPort{
        Param(
            [parameter(Position=0)]
            [string]
            $ComputerName,

            [parameter(Mandatory=$False)]
            [System.Net.IPAddress]
            $IPAddress,

            [parameter(Mandatory=$True , Position=1)]
            [int]
            $Port
            )

        $RemoteServer = If ([string]::IsNullOrEmpty($ComputerName)) {$IPAddress} Else {$ComputerName};
        If ([string]::IsNullOrEmpty($RemoteServer)) {Write-Error "ERROR: Line $(LINENUM): No ComputerName or IPAddress was provided to test."; return}

        $test = New-Object System.Net.Sockets.TcpClient;
        Try
        {
            Write-Output "Connecting to $($RemoteServer):$Port (TCP)..";
            $test.Connect($RemoteServer, $Port);
            Write-Output "Connection successful";
        }
        Catch
        {
            Write-Output "ERROR: Connection failed";
            $Global:PortTestError = 1
        }
        Finally
        {
            $test.Close();
        }

        }#End Function TestPort

        Clear-Variable CleanSvr,svr,proc,processes,port,netstat,line -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"

    }#End Begin

    Process{
        If (-not ($Server) -and (-not ($TrayPort) -or -not ($Quiet))){
            Write-Verbose 'No Server Input - Checking for names.'
            $Server = Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand 'Server' -EA 0
            If (-not ($Server)){
                Write-Verbose 'No Server found in installed Service Info. Checking for Service Backup.'
                $Server = Get-LTServiceInfoBackup -EA 0 -Verbose:$False|Select-Object -Expand 'Server' -EA 0
            }#End If
        }#End If

        If (-not ($Quiet) -or (($TrayPort) -ge 1 -and ($TrayPort) -le 65530)){
            If (-not ($TrayPort) -or -not (($TrayPort) -ge 1 -and ($TrayPort) -le 65530)){
                #Learn LTTrayPort if available.
                $TrayPort = (Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand TrayPort -EA 0)
            }
            If (-not ($TrayPort) -or $TrayPort -notmatch '^\d+$') {$TrayPort=42000}

            [array]$processes = @()
            #Get all processes that are using LTTrayPort (Default 42000)
            Try {$netstat=& "${env:windir}\system32\netstat.exe" -a -o -n | Select-String -Pattern " .*[0-9\.]+:$($TrayPort).*[0-9\.]+:[0-9]+ .*?([0-9]+)" -EA 0}
            Catch {Write-Output "Error calling netstat.exe."; $netstat=$null}
            Foreach ($line In $netstat){
                $processes += ($line -split ' {4,}')[-1]
            }
            $processes = $processes | Where-Object {$_ -gt 0 -and $_ -match '^\d+$'}| Sort-Object | Get-Unique
            If (($processes)) {
                If (-not ($Quiet)){
                    Foreach ($proc In $processes) {
                        If ((Get-Process -ID $proc -EA 0|Select-Object -Expand ProcessName -EA 0) -eq 'LTSvc') {
                            Write-Output "TrayPort Port $TrayPort is being used by LTSvc."
                        } Else {
                            Write-Output "Error: TrayPort Port $TrayPort is being used by $(Get-Process -ID $proc|Select-Object -Expand ProcessName -EA 0)."
                        }#End If
                    }#End Foreach
                } Else {return $False}#End If
            } ElseIf (($Quiet) -eq $True){
                return $True
            } Else {
                Write-Output "TrayPort Port $TrayPort is available."
            }#End If
        }#End If

        foreach ($svr in $Server) {
            if ($Quiet){
                Test-Connection $Svr -Quiet
                return
            }

            If ($Svr -match '^(https?://)?(([12]?[0-9]{1,2}\.){3}[12]?[0-9]{1,2}|[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*)$') {
                Try{
                    $CleanSvr = ($Svr -replace 'https?://',''|ForEach-Object {$_.Trim()})
                    Write-Output "Testing connectivity to required TCP ports:"
                    TestPort -ComputerName $CleanSvr -Port 70
                    TestPort -ComputerName $CleanSvr -Port 80
                    TestPort -ComputerName $CleanSvr -Port 443
                    TestPort -ComputerName mediator.labtechsoftware.com -Port 8002

                }#End Try

                Catch{
                    Write-Error "ERROR: Line $(LINENUM): There was an error testing the ports. $($Error[0])" -ErrorAction Stop
                }#End Catch
            } Else {
                Write-Warning "WARNING: Line $(LINENUM): Server address $($Svr) is not a valid address or is not formatted correctly. Example: https://lt.domain.com"
            }#End If
        }#End Foreach
    }#End Process

    End{
        If ($?){
            if (-not ($Quiet)){
                Write-Output "Test-LTPorts Finished"
            }
        }
        Else{$Error[0]}
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Test-LTPorts

Function Get-LTLogging{
<#
.SYNOPSIS
    This function will return the logging level of the LabTech service.

.NOTES
    Version:        1.1
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/18/2018
    Purpose/Change: Changed Erroraction from Stop to unspecified to allow caller to set the ErrorAction.

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param ()

    Begin{
        Write-Verbose "Checking for registry keys."
    }#End Begin

    Process{
        Try{
            $Value = (Get-LTServiceSettings|Select-Object -Expand Debuging -EA 0)
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was a problem reading the registry key. $($Error[0])"
            return
        }#End Catch
    }#End Process

    End{
        if ($?){
            if ($value -eq 1){
                Write-Output "Current logging level: Normal"
            }
            elseif ($value -eq 1000){
                Write-Output "Current logging level: Verbose"
            }
            else{
                Write-Error "ERROR: Line $(LINENUM): Unknown Logging level $($value)"
            }
        }
    }#End End
}#End Function Get-LTLogging

Function Set-LTLogging{
<#
.SYNOPSIS
        This function will set the logging level of the LabTech service.

.NOTES
    Version:        1.1
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

.LINK
    http://labtechconsulting.com
#>
    Param (
        [switch]$Normal,
        [switch]$Verbose
    )

    Begin{
    if ($Normal -ne $true -and $Verbose -ne $true ){
        Write-Error "ERROR: Line $(LINENUM): Please provide a logging level. -Normal or -Verbose" -ErrorAction Stop
    }
    }#End Begin

    Process{
    Try{
        Stop-LTService
        if ($Normal){
            Set-ItemProperty HKLM:\SOFTWARE\LabTech\Service\Settings -Name 'Debuging' -Value 1
        }
        if ($Verbose){
            Set-ItemProperty HKLM:\SOFTWARE\LabTech\Service\Settings -Name 'Debuging' -Value 1000
        }
        Start-LTService
    }#End Try

    Catch{
      Write-Error "ERROR: Line $(LINENUM): There was a problem writing the registry key. $($Error[0])" -ErrorAction Stop
    }#End Catch
    }#End Process

    End{
    if ($?){
        Get-LTLogging
    }
    }#End End
}#End Function Set-LTLogging

Function Get-LTProbeErrors{
<#
.SYNOPSIS
    This will pull the %ltsvcdir%\LTProbeErrors.txt file into an object.

.EXAMPLE
    Get-LTProbeErrors | where {(Get-date $_.Time) -gt (get-date).AddHours(-24)}
    Get a list of all errors in the last 24hr

.EXAMPLE
    Get-LTProbeErrors | Out-Gridview
    Open the log file in a sortable searchable window.

.NOTES
    Version:        1.3
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  3/14/2016
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/18/2018
    Purpose/Change: Changed Erroraction from Stop to unspecified to allow caller to set the ErrorAction.

    Update Date: 1/26/2019
    Purpose/Change: Update for better international date parsing support

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param()

    Begin{
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
        $BasePath = $(Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand BasePath -EA 0)
        if (!($BasePath)){$BasePath = "${env:windir}\LTSVC"}
    }#End Begin

    Process{
        if ($(Test-Path -Path "$BasePath\LTProbeErrors.txt") -eq $False) {
            Write-Error "ERROR: Line $(LINENUM): Unable to find log."
            return
        }
        $errors = Get-Content "$BasePath\LTProbeErrors.txt"
        $errors = $errors -join ' ' -split '::: '
        Try {
            Foreach($Line in $Errors){
                $items = $Line -split "`t" -replace ' - ',''
                $object = New-Object -TypeName PSObject
                $object | Add-Member -MemberType NoteProperty -Name ServiceVersion -Value $items[0]
                $object | Add-Member -MemberType NoteProperty -Name Timestamp -Value $(Try {[datetime]::Parse($items[1])} Catch {})
                $object | Add-Member -MemberType NoteProperty -Name Message -Value $items[2]
                Write-Output $object
            }#End Foreach
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error reading the log. $($Error[0])"
        }#End Catch
    }

    End{
        if ($?){
        }
        Else {$Error[0]}
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function Get-LTProbeErrors

Function New-LTServiceBackup{
<#
.SYNOPSIS
    This function will backup all the reg keys to 'HKLM\SOFTWARE\LabTechBackup'
    This will also backup those files to "$((Get-LTServiceInfo).BasePath)Backup"

.NOTES
    Version:        1.4
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  5/11/2017
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 6/7/2017
    Purpose/Change: Updated error handling.

    Update Date: 8/24/2017
    Purpose/Change: Update to use Clear-Variable.

    Update Date: 3/21/2017
    Purpose/Change: Added additional Debug Output

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param ()

    Begin{
        Clear-Variable LTPath,BackupPath,Keys,Path,Result,Reg,RegPath -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"

        $LTPath = "$(Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False|Select-Object -Expand BasePath -EA 0)"
        if (-not ($LTPath)) {
            Write-Error "ERROR: Line $(LINENUM): Unable to find LTSvc folder path." -ErrorAction Stop
        }
        $BackupPath = "$($LTPath)Backup"
        $Keys = "HKLM\SOFTWARE\LabTech"
        $RegPath = "$BackupPath\LTBackup.reg"

        Write-Verbose "Checking for registry keys."
        if ((Test-Path ($Keys -replace '^(H[^\\]*)','$1:')) -eq $False){
            Write-Error "ERROR: Line $(LINENUM): Unable to find registry information on LTSvc. Make sure the agent is installed." -ErrorAction Stop
        }
        if ($(Test-Path -Path $LTPath -PathType Container) -eq $False) {
            Write-Error "ERROR: Line $(LINENUM): Unable to find LTSvc folder path $LTPath" -ErrorAction Stop
        }
        New-Item $BackupPath -type directory -ErrorAction SilentlyContinue | Out-Null
        if ($(Test-Path -Path $BackupPath -PathType Container) -eq $False) {
            Write-Error "ERROR: Line $(LINENUM): Unable to create backup folder path $BackupPath" -ErrorAction Stop
        }
    }#End Begin

    Process{
        Try{
            Copy-Item $LTPath $BackupPath -Recurse -Force
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was a problem backing up the LTSvc Folder. $($Error[0])"
        }#End Catch

        Try{
            Write-Debug "Line $(LINENUM): Exporting Registry Data"
            $Null = & "${env:windir}\system32\reg.exe" export "$Keys" "$RegPath" /y 2>''
            Write-Debug "Line $(LINENUM): Loading and modifying registry key name"
            $Reg = Get-Content $RegPath
            $Reg = $Reg -replace [Regex]::Escape('[HKEY_LOCAL_MACHINE\SOFTWARE\LabTech'),'[HKEY_LOCAL_MACHINE\SOFTWARE\LabTechBackup'
            Write-Debug "Line $(LINENUM): Writing output information"
            $Reg | Out-File $RegPath
            Write-Debug "Line $(LINENUM): Importing Registry data to Backup Path"
            $Null = & "${env:windir}\system32\reg.exe" import "$RegPath" 2>''
            $True | Out-Null #Protection to prevent exit status error
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was a problem backing up the LTSvc Registry keys. $($Error[0])"
        }#End Catch
    }#End Process

    End{
        If ($?){
            Write-Output "The LabTech Backup has been created."
        } Else {
            Write-Error "ERROR: Line $(LINENUM): There was a problem completing the LTSvc Backup. $($Error[0])"
        }#End If
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End
}#End Function New-LTServiceBackup

Function Get-LTServiceInfoBackup{
<#
.SYNOPSIS
    This function will pull all of the backed up registry data into an object.

.NOTES
    Version:        1.1
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  5/11/2017
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/18/2018
    Purpose/Change: Changed Erroraction from Stop to unspecified to allow caller to set the ErrorAction.

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param ()

    Begin{
        Write-Verbose "Checking for registry keys."
        $exclude = "PSParentPath","PSChildName","PSDrive","PSProvider","PSPath"
    }#End Begin

    Process{
        If ((Test-Path 'HKLM:\SOFTWARE\LabTechBackup\Service') -eq $False){
            Write-Error "ERROR: Line $(LINENUM): Unable to find backup information on LTSvc. Use New-LTServiceBackup to create a settings backup."
            return
        }
        Try{
            $key = Get-ItemProperty HKLM:\SOFTWARE\LabTechBackup\Service -ErrorAction Stop | Select-Object * -exclude $exclude
            If ($Null -ne $key -and ($key|Get-Member|Where-Object {$_.Name -match 'BasePath'})) {
                $key.BasePath = [System.Environment]::ExpandEnvironmentVariables($key.BasePath) -replace '\\\\','\'
            }
            If ($Null -ne $key -and ($key|Get-Member|Where-Object {$_.Name -match 'Server Address'})) {
                $Servers = ($Key|Select-Object -Expand 'Server Address' -EA 0).Split('|')|ForEach-Object {$_.Trim()}
                Add-Member -InputObject $key -MemberType NoteProperty -Name 'Server' -Value $Servers -Force
            }
        }#End Try
        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was a problem reading the backup registry keys. $($Error[0])"
            return
        }#End Catch
    }#End Process

    End{
        If ($?){
            return $key
        }
    }#End End
}#End Function Get-LTServiceInfoBackup

Function Rename-LTAddRemove{
<#
.SYNOPSIS
    This function renames the LabTech install as shown in the Add/Remove Programs list.

.DESCRIPTION
    This function will change the value of the DisplayName registry key to effect Add/Remove Programs list.

.PARAMETER Name
    This is the Name for the LabTech Agent as displayed in the list of installed software.

.PARAMETER PublisherName
    This is the Name for the Publisher of the LabTech Agent as displayed in the list of installed software.

.NOTES
    Version:        1.2
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  5/14/2017
    Purpose/Change: Initial script development

    Update Date: 6/1/2017
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/12/2018
    Purpose/Change: Support for ShouldProcess to enable -Confirm and -WhatIf.

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param(
        [Parameter(Mandatory=$True)]
        $Name,

        [Parameter(Mandatory=$False)]
        [AllowNull()]
        [string]$PublisherName
    )

    Begin{
        $RegRoots = ('HKLM:\SOFTWARE\Classes\Installer\Products\C4D064F3712D4B64086B5BDE05DBC75F',
        'HKLM:\SOFTWARE\Classes\Installer\Products\D1003A85576B76D45A1AF09A0FC87FAC')
        $PublisherRegRoots = ('HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{58A3001D-B675-4D67-A5A1-0FA9F08CF7CA}',
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{58A3001D-B675-4D67-A5A1-0FA9F08CF7CA}',
        'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{3F460D4C-D217-46B4-80B6-B5ED50BD7CF5}',
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{3F460D4C-D217-46B4-80B6-B5ED50BD7CF5}')
        $RegNameFound=0;
        $RegPublisherFound=0;
    }#End Begin

    Process{
        Try{
            foreach($RegRoot in $RegRoots){
                if (Get-ItemProperty $RegRoot -Name ProductName -ErrorAction SilentlyContinue){
                    If ($PSCmdlet.ShouldProcess("$($RegRoot)\ProductName=$($Name)", "Set Registry Value")) {
                        Write-Verbose "Setting $($RegRoot)\ProductName=$($Name)"
                        Set-ItemProperty $RegRoot -Name ProductName -Value $Name -Confirm:$False
                        $RegNameFound++
                    }#End If
                } ElseIf (Get-ItemProperty $RegRoot -Name HiddenProductName -ErrorAction SilentlyContinue){
                    If ($PSCmdlet.ShouldProcess("$($RegRoot)\HiddenProductName=$($Name)", "Set Registry Value")) {
                        Write-Verbose "Setting $($RegRoot)\HiddenProductName=$($Name)"
                        Set-ItemProperty $RegRoot -Name HiddenProductName -Value $Name -Confirm:$False
                        $RegNameFound++
                    }#End If
                }
            }#End Foreach
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error setting the registry key value. $($Error[0])" -ErrorAction Stop
        }#End Catch

        If (($PublisherName)){
            Try{
                Foreach($RegRoot in $PublisherRegRoots){
                    If (Get-ItemProperty $RegRoot -Name Publisher -ErrorAction SilentlyContinue){
                        If ($PSCmdlet.ShouldProcess("$($RegRoot)\Publisher=$($PublisherName)", "Set Registry Value")) {
                            Write-Verbose "Setting $($RegRoot)\Publisher=$($PublisherName)"
                            Set-ItemProperty $RegRoot -Name Publisher -Value $PublisherName -Confirm:$False
                            $RegPublisherFound++
                        }#End If
                    }#End If
                }#End Foreach
            }#End Try

            Catch{
                Write-Error "ERROR: Line $(LINENUM): There was an error setting the registry key value. $($Error[0])" -ErrorAction Stop
            }#End Catch
        }#End If
    }#End Process

    End{
        If ($WhatIfPreference -ne $True) {
            If ($?){
                If ($RegNameFound -gt 0) {
                    Write-Output "LabTech is now listed as $($Name) in Add/Remove Programs."
                } Else {
                    Write-Warning "WARNING: Line $(LINENUM): LabTech was not found in installed software and the Name was not changed."
                }#End If
                If (($PublisherName)){
                    If ($RegPublisherFound -gt 0) {
                        Write-Output "The Publisher is now listed as $($PublisherName)."
                    } Else {
                        Write-Warning "WARNING: Line $(LINENUM): LabTech was not found in installed software and the Publisher was not changed."
                    }
                }#End If
            } Else {$Error[0]}
        }#End If
    }#End End
}#End Function Rename-LTAddRemove

Function Invoke-LTServiceCommand {
<#
.SYNOPSIS
    This function tells the agent to execute the desired command.

.DESCRIPTION
    This function will allow you to execute all known commands against an agent.

.NOTES
    Version:        1.2
    Author:         Chris Taylor
    Website:        labtechconsulting.com
    Creation Date:  2/2/2018
    Purpose/Change: Initial script development
    Thanks:         Gavin Stone, for finding the command list

    Update Date: 2/8/2018
    Purpose/Change: Updates for better overall compatibility, including better support for PowerShell V2

    Update Date: 3/21/2018
    Purpose/Change: Removed ErrorAction Override

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param(
        [Parameter(Mandatory=$True, Position=1, ValueFromPipeline=$True)]
        [ValidateSet("Update Schedule",
                     "Send Inventory",
                     "Send Drives",
                     "Send Processes",
                     "Send Spyware List",
                     "Send Apps",
                     "Send Events",
                     "Send Printers",
                     "Send Status",
                     "Send Screen",
                     "Send Services",
                     "Analyze Network",
                     "Write Last Contact Date",
                     "Kill VNC",
                     "Kill Trays",
                     "Send Patch Reboot",
                     "Run App Care Update",
                     "Start App Care Daytime Patching")][string[]]$Command
    )

    Begin {
        $Service = Get-Service 'LTService'
    }

    Process {
        If (-not ($Service)) {Write-Warning "WARNING: Line $(LINENUM): Service 'LTService' was not found. Cannot send service command"; return}
        If ($Service.Status -ne 'Running') {Write-Warning "WARNING: Line $(LINENUM): Service 'LTService' is not running. Cannot send service command"; return}
        Foreach ($Cmd in $Command) {
            $CommandID=$Null
            Try{
                switch($Cmd){
                    'Update Schedule' {$CommandID = 128}
                    'Send Inventory' {$CommandID = 129}
                    'Send Drives' {$CommandID = 130}
                    'Send Processes' {$CommandID = 131}
                    'Send Spyware List'{$CommandID = 132}
                    'Send Apps' {$CommandID = 133}
                    'Send Events' {$CommandID = 134}
                    'Send Printers' {$CommandID = 135}
                    'Send Status' {$CommandID = 136}
                    'Send Screen' {$CommandID = 137}
                    'Send Services' {$CommandID = 138}
                    'Analyze Network' {$CommandID = 139}
                    'Write Last Contact Date' {$CommandID = 140}
                    'Kill VNC' {$CommandID = 141}
                    'Kill Trays' {$CommandID = 142}
                    'Send Patch Reboot' {$CommandID = 143}
                    'Run App Care Update' {$CommandID = 144}
                    'Start App Care Daytime Patching' {$CommandID = 145}
                    default {"Invalid entry"}
                }
                If ($PSCmdlet.ShouldProcess("LTService", "Send Service Command '$($Cmd)' ($($CommandID))")) {
                    If ($Null -ne $CommandID) {
                        Write-Debug "Line $(LINENUM): Sending service command '$($Cmd)' ($($CommandID)) to 'LTService'"
                        Try {
                            $Null=& "${env:windir}\system32\sc.exe" control LTService $($CommandID) 2>''
                            Write-Output "Sent Command '$($Cmd)' to 'LTService'"
                        }
                        Catch {
                            Write-Output "Error calling sc.exe. Failed to send command."
                        }
                    }#End If
                }#End If
            } # End Try

            Catch{
                Write-Warning ("WARNING: Line $(LINENUM)",$_.Exception)
            } # End Catch
        } # End Foreach
    } # End Process

    End{}

} # End Function Invoke-LTServiceCommand

Function Initialize-LTServiceKeys{
<#
.SYNOPSIS
    This function initializes internal variables needed by other functions

.DESCRIPTION
    This function will set variables for the Agent and Server passwords needed
    for encoding and decoding steps. Nothing is returned.

.NOTES
    Version:        1.1
    Author:         Darren White
    Creation Date:  1/25/2018
    Purpose/Change: Initial function development

    Update Date: 3/18/2018
    Purpose/Change: Rename to Initialize-LTServiceKeys from Get-LTServiceKeys

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param(
    )

    Process {
        $LTSI=Get-LTServiceInfo -EA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False
        If (($LTSI) -and ($LTSI|Get-Member|Where-Object {$_.Name -eq 'ServerPassword'})) {
            Write-Debug "Line $(LINENUM): Decoding Server Password."
            $Script:LTServiceKeys.ServerPasswordString=$(ConvertFrom-LTSecurity -InputString "$($LTSI.ServerPassword)")
            If ($Null -ne $LTSI -and ($LTSI|Get-Member|Where-Object {$_.Name -eq 'Password'})) {
                Write-Debug "Line $(LINENUM): Decoding Agent Password."
                $Script:LTServiceKeys.PasswordString=$(ConvertFrom-LTSecurity -InputString "$($LTSI.Password)" -Key "$($Script:LTServiceKeys.ServerPasswordString)")
            } Else {
                $Script:LTServiceKeys.PasswordString=''
            }
        } Else {
            $Script:LTServiceKeys.ServerPasswordString=''
            $Script:LTServiceKeys.PasswordString=''
        }
    }#End Process

    End {
    }#End End
}#End Function Initialize-LTServiceKeys

Function ConvertFrom-LTSecurity{
<#
.SYNOPSIS
    This function decodes an encoded Base64 value

.DESCRIPTION
    This function decodes the provided string using the specified or default key.

.PARAMETER InputString
    This is the string to be decoded.

.PARAMETER Key
    This is the key used for decoding. If not provided, default values will be tried.

.PARAMETER Force
    This forces the function to try alternate key values if decoding fails using provided key.

.NOTES
    Version:        1.2
    Author:         Darren White
    Creation Date:  1/25/2018
    Purpose/Change: Initial function development

    Update Date: 6/25/2020
    Purpose/Change: Improved pipeline and parameter handling

.LINK
    http://labtechconsulting.com
#>
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
    [parameter(Mandatory = $true, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Position = 1)]
    [string[]]$InputString,

    [parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $True)]
    [AllowNull()]
    [AllowEmptyString()]
    [AllowEmptyCollection()]
    $Key = $Null,

    [parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
    [switch]$Force=$True
)

    Begin {
        $DefaultKey='Thank you for using LabTech.'
        $_initializationVector = [byte[]](240, 3, 45, 29, 0, 76, 173, 59)
        $NoKeyPassed=$False
        $DecodedString=$Null
        $DecodeString=$Null
    }#End Begin

    Process {
        If ($Null -eq $Key) {
            $NoKeyPassed=$True
            $Key=$DefaultKey
        }
        foreach ($testInput in $InputString) {
            $DecodeString=$Null
            foreach ($testKey in $Key) {
                If ($Null -eq $DecodeString) {
                    If ($Null -eq $testKey) {
                        $NoKeyPassed=$True
                        $testKey=$DefaultKey
                    }#End If
                    Write-Debug "Line $(LINENUM): Attempting Decode for '$($testInput)' with Key '$($testKey)'"
                    Try {
                        $numarray=[System.Convert]::FromBase64String($testInput)
                        $ddd = new-object System.Security.Cryptography.TripleDESCryptoServiceProvider
                        $ddd.key=(new-Object Security.Cryptography.MD5CryptoServiceProvider).ComputeHash([Text.Encoding]::UTF8.GetBytes($testKey))
                        $ddd.IV=$_initializationVector
                        $dd=$ddd.CreateDecryptor()
                        $DecodeString=[System.Text.Encoding]::UTF8.GetString($dd.TransformFinalBlock($numarray,0,($numarray.Length)))
                        $DecodedString+=@($DecodeString)
                    } Catch {
                    }#End Catch

                    Finally {
                        if ((Get-Variable -Name dd -Scope 0 -EA 0)) {try {$dd.Dispose()} catch {$dd.Clear()}}
                        if ((Get-Variable -Name ddd -Scope 0 -EA 0)) {try {$ddd.Dispose()} catch {$ddd.Clear()}}
                    }#End Finally
                } Else {
                }#End If
            }#End foreach
            If ($Null -eq $DecodeString) {
                If ($Force) {
                    If (($NoKeyPassed)) {
                        $DecodeString=ConvertFrom-LTSecurity -InputString "$($testInput)" -Key '' -Force:$False
                        If (-not ($Null -eq $DecodeString)) {
                            $DecodedString+=@($DecodeString)
                        }
                    } Else {
                        $DecodeString=ConvertFrom-LTSecurity -InputString "$($testInput)"
                        if (-not ($Null -eq $DecodeString)) {
                            $DecodedString+=@($DecodeString)
                        }
                    }#End If
                } Else {
                }#End If
            }#End If
        }#End foreach
    }#End Process

    End {
        If ($Null -eq $DecodedString) {
            Write-Debug "Line $(LINENUM): Failed to Decode string: '$($InputString)'"
            return $Null
        } else {
            return $DecodedString
        }#End If
    }#End End

}#End Function ConvertFrom-LTSecurity

Function ConvertTo-LTSecurity{
<#
.SYNOPSIS
    This function encodes a value compatible with LT operations.

.DESCRIPTION
    This function encodes the provided string using the specified or default key.

.PARAMETER InputString
    This is the string to be encoded.

.PARAMETER Key
    This is the key used for encoding. If not provided, a default value will be used.

.NOTES
    Version:        1.2
    Author:         Darren White
    Creation Date:  1/25/2018
    Purpose/Change: Initial function development

    Update Date: 6/23/2020
    Purpose/Change: Improved pipeline and parameter handling

.LINK
    http://labtechconsulting.com
#>
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
    [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1)]
    [AllowNull()]
    [AllowEmptyString()]
    [AllowEmptyCollection()]
    [string[]]$InputString,
    [parameter(Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $true)]
    [AllowNull()]
    [AllowEmptyString()]
    [AllowEmptyCollection()]
    $Key = $Null
)

    Begin {
        $_initializationVector = [byte[]](240, 3, 45, 29, 0, 76, 173, 59)
        $DefaultKey='Thank you for using LabTech.'
        $str=@()
    }#End Begin

    Process {
        foreach ($testInput in $InputString) {
            If ($Null -eq $Key) {
                $Key=$DefaultKey
            }#End If

            try {
                $numarray=[System.Text.Encoding]::UTF8.GetBytes($testInput)
            } catch {
                try { $numarray=[System.Text.Encoding]::ASCII.GetBytes($testInput) } catch {}
            }
            Write-Debug "Line $(LINENUM): Attempting Encode for '$($testInput)' with Key '$($Key)'"
            try {
                $ddd = new-object System.Security.Cryptography.TripleDESCryptoServiceProvider
                $ddd.key=(new-Object Security.Cryptography.MD5CryptoServiceProvider).ComputeHash([Text.Encoding]::UTF8.GetBytes($Key))
                $ddd.IV=$_initializationVector
                $dd=$ddd.CreateEncryptor()
                $str+=[System.Convert]::ToBase64String($dd.TransformFinalBlock($numarray,0,($numarray.Length)))
            }
            catch {
                Write-Debug "Line $(LINENUM): Failed to Encode string: '$($InputString)'"
                $str+=''
            }
            Finally
            {
                if ($dd) {try {$dd.Dispose()} catch {$dd.Clear()}}
                if ($ddd) {try {$ddd.Dispose()} catch {$ddd.Clear()}}
            }
        }#End ForEach
    }#End Process

    End {
        return $str
    }#End End
}#End Function ConvertTo-LTSecurity

Function Set-LTProxy{
<#
.SYNOPSIS
    This function configures module functions to use the specified proxy
    configuration for all operations as long as the module remains loaded.

.DESCRIPTION
    This function will set or clear Proxy settings needed for function and
    agent operations. If an agent is already installed, this function will
    set the ProxyUsername, ProxyPassword, and ProxyServerURL values for the
    Agent.
    NOTE: Agent Services will be restarted for changes (if found) to be applied.

.PARAMETER ProxyServerURL
    This is the URL and Port to assign as the ProxyServerURL for Module
    operations during this session and for the Installed Agent (if present).
    Example: Set-LTProxy -ProxyServerURL 'proxyhostname.fqdn.com'
    Example: Set-LTProxy -ProxyServerURL 'proxyhostname.fqdn.com:8080'
    This parameter may be used with the additional following parameters:
    ProxyUsername, ProxyPassword, EncodedProxyUsername, EncodedProxyPassword

.PARAMETER ProxyUsername
    This is the plain text Username for Proxy operations.
    Example: Set-LTProxy -ProxyServerURL 'proxyhostname.fqdn.com:8080' -ProxyUsername 'Test-User' -ProxyPassword 'SomeFancyPassword'

.PARAMETER ProxyPassword
    This is the plain text Password for Proxy operations.

.PARAMETER EncodedProxyUsername
    This is the encoded Username for Proxy operations. The parameter must be
    encoded with the Agent Password. This Parameter will be decoded using the
    Agent Password, and the decoded string will be configured.
    NOTE: Reinstallation of the Agent will generate a new agent password.
    Example: Set-LTProxy -ProxyServerURL 'proxyhostname.fqdn.com:8080' -EncodedProxyUsername '1GzhlerwMy0ElG9XNgiIkg==' -EncodedProxyPassword 'Duft4r7fekTp5YnQL9F0V9TbP7sKzm0n'

.PARAMETER EncodedProxyPassword
    This is the encoded Password for Proxy operations. The parameter must be
    encoded with the Agent Password. This Parameter will be decoded using the
    Agent Password, and the decoded string will be configured.
    NOTE: Reinstallation of the Agent will generate a new password.

.PARAMETER DetectProxy
    This parameter attempts to automatically detect the system Proxy settings
    for Module operations during this session. Discovered settings will be
    assigned to the Installed Agent (if present).
    Example: Set-LTProxy -DetectProxy
    This parameter may not be used with other parameters.

.PARAMETER ResetProxy
    This parameter clears any currently defined Proxy Settings for Module
    operations during this session. Discovered settings will be assigned
    to the Installed Agent (if present).
    Example: Set-LTProxy -ResetProxy
    This parameter may not be used with other parameters.

.NOTES
    Version:        1.1
    Author:         Darren White
    Creation Date:  1/24/2018
    Purpose/Change: Initial function development

.LINK
    http://labtechconsulting.com
#>

    [CmdletBinding(SupportsShouldProcess=$True)]
    Param(
        [parameter(Mandatory = $False, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, Position = 0)]
        [string]$ProxyServerURL,

        [parameter(Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Position = 1)]
        [string]$ProxyUsername,

        [parameter(Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Position = 2)]
        [string]$ProxyPassword,

        [parameter(Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)]
        [string]$EncodedProxyUsername,

        [parameter(Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)]
        [string]$EncodedProxyPassword,

        [parameter(Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)]
        [alias('Detect')]
        [alias('AutoDetect')]
        [switch]$DetectProxy,

        [parameter(Mandatory = $False, ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True)]
        [alias('Clear')]
        [alias('Reset')]
        [alias('ClearProxy')]
        [switch]$ResetProxy
    )

    Begin {
        Clear-Variable LTServiceSettingsChanged,LTSS,LTServiceRestartNeeded,proxyURL,proxyUser,proxyPass,passwd,Svr -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"

        try {
            $LTSS=Get-LTServiceSettings -EA 0 -Verbose:$False -WA 0 -Debug:$False
        } catch {}

    }#End Begin

    Process{

        If (
(($ResetProxy -eq $True) -and (($DetectProxy -eq $True) -or ($ProxyServerURL) -or ($ProxyUsername) -or ($ProxyPassword) -or ($EncodedProxyUsername) -or ($EncodedProxyPassword))) -or
(($DetectProxy -eq $True) -and (($ResetProxy -eq $True) -or ($ProxyServerURL) -or ($ProxyUsername) -or ($ProxyPassword) -or ($EncodedProxyUsername) -or ($EncodedProxyPassword))) -or
((($ProxyServerURL) -or ($ProxyUsername) -or ($ProxyPassword) -or ($EncodedProxyUsername) -or ($EncodedProxyPassword)) -and (($ResetProxy -eq $True) -or ($DetectProxy -eq $True))) -or
((($ProxyUsername) -or ($ProxyPassword)) -and (-not ($ProxyServerURL) -or ($EncodedProxyUsername) -or ($EncodedProxyPassword) -or ($ResetProxy -eq $True) -or ($DetectProxy -eq $True))) -or
((($EncodedProxyUsername) -or ($EncodedProxyPassword)) -and (-not ($ProxyServerURL) -or ($ProxyUsername) -or ($ProxyPassword) -or ($ResetProxy -eq $True) -or ($DetectProxy -eq $True)))
        ) {Write-Error "ERROR: Line $(LINENUM): Set-LTProxy: Invalid Parameter specified" -ErrorAction Stop}
        If (-not (($ResetProxy -eq $True) -or ($DetectProxy -eq $True) -or ($ProxyServerURL) -or ($ProxyUsername) -or ($ProxyPassword) -or ($EncodedProxyUsername) -or ($EncodedProxyPassword)))
        {
            If ($Args.Count -gt 0) {Write-Error "ERROR: Line $(LINENUM): Set-LTProxy: Unknown Parameter specified" -ErrorAction Stop}
            Else {Write-Error "ERROR: Line $(LINENUM): Set-LTProxy: Required Parameters Missing" -ErrorAction Stop}
        }

        Try{
            If ($($ResetProxy) -eq $True) {
                Write-Verbose "ResetProxy selected. Clearing Proxy Settings."
                If ( $PSCmdlet.ShouldProcess("LTProxy", "Clear") ) {
                    $Script:LTProxy.Enabled=$False
                    $Script:LTProxy.ProxyServerURL=''
                    $Script:LTProxy.ProxyUsername=''
                    $Script:LTProxy.ProxyPassword=''
                    $Script:LTWebProxy=New-Object System.Net.WebProxy
                    $Script:LTServiceNetWebClient.Proxy=$Script:LTWebProxy
                }#End If
            } ElseIf ($($DetectProxy) -eq $True) {
                Write-Verbose "DetectProxy selected. Attempting to Detect Proxy Settings."
                If ( $PSCmdlet.ShouldProcess("LTProxy", "Detect") ) {
                    $Script:LTWebProxy=[System.Net.WebRequest]::GetSystemWebProxy()
                    $Script:LTProxy.Enabled=$False
                    $Script:LTProxy.ProxyServerURL=''
                    $Servers = @($("$($LTSS|Select-Object -Expand 'ServerAddress' -EA 0)|www.connectwise.com").Split('|')|ForEach-Object {$_.Trim()})
                    Foreach ($Svr In $Servers) {
                        If (-not ($Script:LTProxy.Enabled)) {
                            If ($Svr -match '^(https?://)?(([12]?[0-9]{1,2}\.){3}[12]?[0-9]{1,2}|[a-z0-9][a-z0-9_-]*(\.[a-z0-9][a-z0-9_-]*)*)$') {
                                $Svr = $Svr -replace 'https?://',''
                                Try{
                                    $Script:LTProxy.ProxyServerURL=$Script:LTWebProxy.GetProxy("http://$($Svr)").Authority
                                } catch {}
                                If (($Null -ne $Script:LTProxy.ProxyServerURL) -and ($Script:LTProxy.ProxyServerURL -ne '') -and ($Script:LTProxy.ProxyServerURL -notcontains "$($Svr)")) {
                                    Write-Debug "Line $(LINENUM): Detected Proxy URL: $($Script:LTProxy.ProxyServerURL) on server $($Svr)"
                                    $Script:LTProxy.Enabled=$True
                                }#End If
                            }#End If
                        }#End If
                    }#End Foreach
                    If (-not ($Script:LTProxy.Enabled)) {
                        if (($Script:LTProxy.ProxyServerURL -eq '') -or ($Script:LTProxy.ProxyServerURL -contains '$Svr')) {
                            $Script:LTProxy.ProxyServerURL = netsh winhttp show proxy | select-string -pattern '(?i)(?<=Proxyserver.*http\=)([^;\r\n]*)' -EA 0|ForEach-Object {$_.matches}|Select-Object -Expand value
                        }
                        if (($Null -eq $Script:LTProxy.ProxyServerURL) -or ($Script:LTProxy.ProxyServerURL -eq '')) {
                            $Script:LTProxy.ProxyServerURL=''
                            $Script:LTProxy.Enabled=$False
                        } else {
                            $Script:LTProxy.Enabled=$True
                            Write-Debug "Line $(LINENUM): Detected Proxy URL: $($Script:LTProxy.ProxyServerURL)"
                        }
                    }#End If
                    $Script:LTProxy.ProxyUsername=''
                    $Script:LTProxy.ProxyPassword=''
                    $Script:LTServiceNetWebClient.Proxy=$Script:LTWebProxy
                }#End If
            } ElseIf (($ProxyServerURL)) {
                If ( $PSCmdlet.ShouldProcess("LTProxy", "Set") ) {
                    foreach ($ProxyURL in $ProxyServerURL) {
                        $Script:LTWebProxy = New-Object System.Net.WebProxy($ProxyURL, $true);
                        $Script:LTProxy.Enabled=$True
                        $Script:LTProxy.ProxyServerURL=$ProxyURL
                    }
                    Write-Verbose "Setting Proxy URL to: $($ProxyServerURL)"
                    If ((($ProxyUsername) -and ($ProxyPassword)) -or (($EncodedProxyUsername) -and ($EncodedProxyPassword))) {
                        If (($ProxyUsername)) {
                            foreach ($proxyUser in $ProxyUsername) {
                                $Script:LTProxy.ProxyUsername=$proxyUser
                            }
                        }
                        If (($EncodedProxyUsername)) {
                            foreach ($proxyUser in $EncodedProxyUsername) {
                                $Script:LTProxy.ProxyUsername=$(ConvertFrom-LTSecurity -InputString "$($proxyUser)" -Key ("$($Script:LTServiceKeys.PasswordString)",''))
                            }
                        }
                        If (($ProxyPassword)) {
                            foreach ($proxyPass in $ProxyPassword) {
                                $Script:LTProxy.ProxyPassword=$proxyPass
                                $passwd = ConvertTo-SecureString $proxyPass -AsPlainText -Force; ## Website credentials
                            }
                        }
                        If (($EncodedProxyPassword)) {
                            foreach ($proxyPass in $EncodedProxyPassword) {
                                $Script:LTProxy.ProxyPassword=$(ConvertFrom-LTSecurity -InputString "$($proxyPass)" -Key ("$($Script:LTServiceKeys.PasswordString)",''))
                                $passwd = ConvertTo-SecureString $Script:LTProxy.ProxyPassword -AsPlainText -Force; ## Website credentials
                            }
                        }
                        $Script:LTWebProxy.Credentials = New-Object System.Management.Automation.PSCredential ($Script:LTProxy.ProxyUsername, $passwd);
                    }#End If
                    $Script:LTServiceNetWebClient.Proxy=$Script:LTWebProxy
                }#End If
            }#End If
        }#End Try

        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was an error during the Proxy Configuration process. $($Error[0])" -ErrorAction Stop
        }#End Catch
    }#End Process

    End{
        If ($?){
            $LTServiceSettingsChanged=$False
            If ($Null -ne ($LTSS)) {
                If (($LTSS|Get-Member|Where-Object {$_.Name -eq 'ProxyServerURL'})) {
                    If (($($LTSS|Select-Object -Expand ProxyServerURL -EA 0) -replace 'https?://','' -ne $Script:LTProxy.ProxyServerURL) -and (($($LTSS|Select-Object -Expand ProxyServerURL -EA 0) -replace 'https?://','' -eq '' -and $Script:LTProxy.Enabled -eq $True -and $Script:LTProxy.ProxyServerURL -match '.+\..+') -or ($($LTSS|Select-Object -Expand ProxyServerURL -EA 0) -replace 'https?://','' -ne '' -and ($Script:LTProxy.ProxyServerURL -ne '' -or $Script:LTProxy.Enabled -eq $False)))) {
                        Write-Debug "Line $(LINENUM): ProxyServerURL Changed: Old Value: $($LTSS|Select-Object -Expand ProxyServerURL -EA 0) New Value: $($Script:LTProxy.ProxyServerURL)"
                        $LTServiceSettingsChanged=$True
                    }#End If
                    If (($LTSS|Get-Member|Where-Object {$_.Name -eq 'ProxyUsername'}) -and ($LTSS|Select-Object -Expand ProxyUsername -EA 0)) {
                        If ($(ConvertFrom-LTSecurity -InputString "$($LTSS|Select-Object -Expand ProxyUsername -EA 0)" -Key ("$($Script:LTServiceKeys.PasswordString)",'')) -ne $Script:LTProxy.ProxyUsername) {
                            Write-Debug "Line $(LINENUM): ProxyUsername Changed: Old Value: $(ConvertFrom-LTSecurity -InputString "$($LTSS|Select-Object -Expand ProxyUsername -EA 0)" -Key ("$($Script:LTServiceKeys.PasswordString)",'')) New Value: $($Script:LTProxy.ProxyUsername)"
                            $LTServiceSettingsChanged=$True
                        }
                    }#End If
                    If ($Null -ne ($LTSS) -and ($LTSS|Get-Member|Where-Object {$_.Name -eq 'ProxyPassword'}) -and ($LTSS|Select-Object -Expand ProxyPassword -EA 0)) {
                        If ($(ConvertFrom-LTSecurity -InputString "$($LTSS|Select-Object -Expand ProxyPassword -EA 0)" -Key ("$($Script:LTServiceKeys.PasswordString)",'')) -ne $Script:LTProxy.ProxyPassword) {
                            Write-Debug "Line $(LINENUM): ProxyPassword Changed: Old Value: $(ConvertFrom-LTSecurity -InputString "$($LTSS|Select-Object -Expand ProxyPassword -EA 0)" -Key ("$($Script:LTServiceKeys.PasswordString)",'')) New Value: $($Script:LTProxy.ProxyPassword)"
                            $LTServiceSettingsChanged=$True
                        }
                    }#End If
                } ElseIf ($Script:LTProxy.Enabled -eq $True -and $Script:LTProxy.ProxyServerURL -match '(https?://)?.+\..+') {
                    Write-Debug "Line $(LINENUM): ProxyServerURL Changed: Old Value: NOT SET New Value: $($Script:LTProxy.ProxyServerURL)"
                    $LTServiceSettingsChanged=$True
                }#End If
            } Else {
                $svcRun = ('LTService') | Get-Service -EA 0 | Where-Object {$_.Status -eq 'Running'} | Measure-Object | Select-Object -Expand Count
                if (($svcRun -gt 0) -and ($($Script:LTProxy.ProxyServerURL) -match '.+')) {
                    $LTServiceSettingsChanged=$True
                }#End If
            }#End If
            If ($LTServiceSettingsChanged -eq $True) {
                If ((Get-Service 'LTService','LTSvcMon' -ErrorAction SilentlyContinue|Where-Object {$_.Status -match 'Running'})) { $LTServiceRestartNeeded=$True; try {Stop-LTService -EA 0 -WA 0} catch {} }
                Write-Verbose "Updating LabTech\Service\Settings Proxy Configuration."
                If ( $PSCmdlet.ShouldProcess("LTService Registry", "Update") ) {
                    $Svr=$($Script:LTProxy.ProxyServerURL); If (($Svr -ne '') -and ($Svr -notmatch 'https?://')) {$Svr = "http://$($Svr)"}
                    @{"ProxyServerURL"=$Svr;
                    "ProxyUserName"="$(ConvertTo-LTSecurity -InputString "$($Script:LTProxy.ProxyUserName)" -Key "$($Script:LTServiceKeys.PasswordString)")";
                    "ProxyPassword"="$(ConvertTo-LTSecurity -InputString "$($Script:LTProxy.ProxyPassword)" -Key "$($Script:LTServiceKeys.PasswordString)")"}.GetEnumerator() | Foreach-Object {
                        Write-Debug "Line $(LINENUM): Setting Registry value for $($_.Name) to `"$($_.Value)`""
                        Set-ItemProperty -Path 'HKLM:Software\LabTech\Service\Settings' -Name $($_.Name) -Value $($_.Value) -EA 0 -Confirm:$False
                    }#End Foreach-Object
                }#End If
                If ($LTServiceRestartNeeded -eq $True) { try {Start-LTService -EA 0 -WA 0} catch {} }
            }#End If
        }#End If
        Else {$Error[0]}
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
    }#End End

}#End Function Set-LTProxy

Function Get-LTProxy{
<#
.SYNOPSIS
    This function retrieves the current agent proxy settings for module functions
    to use the specified proxy configuration for all communication operations as
    long as the module remains loaded.

.DESCRIPTION
    This function will get the current LabTech Proxy settings from the
    installed agent (if present). If no agent settings are found, the function
    will attempt to discover the current proxy settings for the system.
    The Proxy Settings determined will be stored in memory for internal use, and
    returned as the function result.

.NOTES
    Version:        1.1
    Author:         Darren White
    Creation Date:  1/24/2018
    Purpose/Change: Initial function development

    Update Date: 3/18/2018
    Purpose/Change: Ensure ProxyUser and ProxyPassword are set correctly when proxy
    is not configured.

.LINK
    http://labtechconsulting.com
#>
    [CmdletBinding()]
    Param(
    )

    Begin{
        Clear-Variable CustomProxyObject,LTSI,LTSS -EA 0 -WhatIf:$False -Confirm:$False #Clearing Variables for use
        Write-Debug "Starting $($myInvocation.InvocationName) at line $(LINENUM)"
        Write-Verbose "Discovering Proxy Settings used by the LT Agent."
        $Null=Initialize-LTServiceKeys
    }#End Begin

    Process{
        Try {
            $LTSI=Get-LTServiceInfo -EA 0 -WA 0 -Verbose:$False -WhatIf:$False -Confirm:$False -Debug:$False
            If ($Null -ne $LTSI -and ($LTSI|Get-Member|Where-Object {$_.Name -eq 'ServerPassword'})) {
                $LTSS=Get-LTServiceSettings -EA 0 -Verbose:$False -WA 0 -Debug:$False
                If ($Null -ne $LTSS) {
                    If (($LTSS|Get-Member|Where-Object {$_.Name -eq 'ProxyServerURL'}) -and ($($LTSS|Select-Object -Expand ProxyServerURL -EA 0) -Match 'https?://.+')) {
                        Write-Debug "Line $(LINENUM): Proxy Detected. Setting ProxyServerURL to $($LTSS|Select-Object -Expand ProxyServerURL -EA 0)"
                        $Script:LTProxy.Enabled=$True
                        $Script:LTProxy.ProxyServerURL="$($LTSS|Select-Object -Expand ProxyServerURL -EA 0)"
                    } Else {
                        Write-Debug "Line $(LINENUM): Setting ProxyServerURL to "
                        $Script:LTProxy.Enabled=$False
                        $Script:LTProxy.ProxyServerURL=''
                    }#End If
                    if ($Script:LTProxy.Enabled -eq $True -and ($LTSS|Get-Member|Where-Object {$_.Name -eq 'ProxyUsername'}) -and ($LTSS|Select-Object -Expand ProxyUsername -EA 0)) {
                        $Script:LTProxy.ProxyUsername="$(ConvertFrom-LTSecurity -InputString "$($LTSS|Select-Object -Expand ProxyUsername -EA 0)" -Key ("$($Script:LTServiceKeys.PasswordString)",''))"
                        Write-Debug "Line $(LINENUM): Setting ProxyUsername to $($Script:LTProxy.ProxyUsername)"
                    } Else {
                        Write-Debug "Line $(LINENUM): Setting ProxyUsername to "
                        $Script:LTProxy.ProxyUsername=''
                    }#End If
                    If ($Script:LTProxy.Enabled -eq $True -and ($LTSS|Get-Member|Where-Object {$_.Name -eq 'ProxyPassword'}) -and ($LTSS|Select-Object -Expand ProxyPassword -EA 0)) {
                        $Script:LTProxy.ProxyPassword="$(ConvertFrom-LTSecurity -InputString "$($LTSS|Select-Object -Expand ProxyPassword -EA 0)" -Key ("$($Script:LTServiceKeys.PasswordString)",''))"
                        Write-Debug "Line $(LINENUM): Setting ProxyPassword to $($Script:LTProxy.ProxyPassword)"
                    } Else {
                        Write-Debug "Line $(LINENUM): Setting ProxyPassword to "
                        $Script:LTProxy.ProxyPassword=''
                    }#End If
                }#End If
            } Else {
                Write-Verbose "No Server password or settings exist. No Proxy information will be available."
            }#End If
        }#End Try
        Catch{
            Write-Error "ERROR: Line $(LINENUM): There was a problem retrieving Proxy Information. $($Error[0])"
        }#End Catch
    }#End Process

    End{
        Write-Debug "Exiting $($myInvocation.InvocationName) at line $(LINENUM)"
        return $Script:LTProxy
    }#End End
}#End Function Get-LTProxy

Function Get-CurrentLineNumber {
    $MyInvocation.ScriptLineNumber
}
Set-Alias -name LINENUM -value Get-CurrentLineNumber -WhatIf:$False -Confirm:$False -Scope Script

Function Initialize-LTServiceModule{
<#
.SYNOPSIS
    This function initializes internal variables needed by other functions

.DESCRIPTION
    This function will set variables for the Agent and Server passwords needed
    for encoding and decoding steps.

.NOTES
    Version:        1.1
    Author:         Darren White
    Creation Date:  1/25/2018
    Purpose/Change: Initial function development

    Update Date: 3/18/2018
    Purpose/Change: Rename to Initialize-LTServiceKeys

.LINK
    http://labtechconsulting.com
#>

    #Populate $Script:LTServiceKeys Object
    $Script:LTServiceKeys = New-Object -TypeName PSObject
    Add-Member -InputObject $Script:LTServiceKeys -MemberType NoteProperty -Name ServerPasswordString -Value ''
    Add-Member -InputObject $Script:LTServiceKeys -MemberType NoteProperty -Name PasswordString -Value ''

    #Populate $Script:LTProxy Object
    Try{
        $Script:LTProxy = New-Object -TypeName PSObject
        Add-Member -InputObject $Script:LTProxy -MemberType NoteProperty -Name ProxyServerURL -Value ''
        Add-Member -InputObject $Script:LTProxy -MemberType NoteProperty -Name ProxyUsername -Value ''
        Add-Member -InputObject $Script:LTProxy -MemberType NoteProperty -Name ProxyPassword -Value ''
        Add-Member -InputObject $Script:LTProxy -MemberType NoteProperty -Name Enabled -Value ''

        #Populate $Script:LTWebProxy Object
        $Script:LTWebProxy=new-object System.Net.WebProxy

        #Initialize $Script:LTServiceNetWebClient Object
        $Script:LTServiceNetWebClient = New-Object System.Net.WebClient
        $Script:LTServiceNetWebClient.Proxy=$Script:LTWebProxy
    } Catch {
        Write-Error "ERROR: Line $(LINENUM): Failed Initializing internal Proxy Objects/Variables."
    }

    $Null=Get-LTProxy -ErrorAction Continue

}#End Initialize-LTServiceModule

#endregion Functions

$PublicFunctions=@(((@"
ConvertFrom-LTSecurity
ConvertTo-LTSecurity
Get-LTErrors
Get-LTLogging
Get-LTProbeErrors
Get-LTProxy
Get-LTServiceInfo
Get-LTServiceInfoBackup
Get-LTServiceSettings
Hide-LTAddRemove
Install-LTService
Invoke-LTServiceCommand
New-LTServiceBackup
Redo-LTService
Rename-LTAddRemove
Reset-LTService
Restart-LTService
Set-LTLogging
Set-LTProxy
Show-LTAddRemove
Start-LTService
Stop-LTService
Test-LTPorts
Uninstall-LTService
Update-LTService
"@) -replace "[`r`n,\s]+",',') -split ',')

$PublicAlias=@(((@"
Get-LTError
ReInstall-LTService
"@) -replace "[`r`n,\s]+",',') -split ',')

If (($MyInvocation.Line -match 'Import-Module' -or $MyInvocation.MyCommand -match 'Import-Module') -and -not ($MyInvocation.Line -match $ModuleGuid -or $MyInvocation.MyCommand -match $ModuleGuid)) {
    # Only export module members when being loaded as a module
    Export-ModuleMember -Function $PublicFunctions -Alias $PublicAlias -EA 0 -WA 0

<#
'Just a small code block to use when developing new features to ensure new functions are not missed.
'Here just so that I don't need to track it down when I want it. - DJW
    $UnPublicFunctions=(Get-Content 'Script Source' | Select-String -Pattern '(?<=^function )[-\w]+' -AllMatches | Select-Object -expand matches) | ForEach-Object {if ($PublicFunctions -notcontains $_.value) {$_.value}};
    if ($UnPublicFunctions) {Write-Debug "Not publishing functions: $(($UnPublicFunctions) -join ',')"}
#>
}

$Null=Initialize-LTServiceModule

#SneakyRun - Save as FUNCTION.ps1 and call FUNCTION.ps1, and it will be treated as if you called FUNCTION directly.
#Maybe it's not that special. You can dot-source the script and call any function directly.  
#Example: Name it "Install-LTService.ps1", then you can call it as the file and it will run that function.
if ($PSCommandPath -like '*.ps1' -and $PSCommandPath -like "*$($MyInvocation.MyCommand)") {
    $LabTechFunction=$MyInvocation.MyCommand.ToString() -replace '\.ps1',''
    If ($PublicFunctions -contains $LabTechFunction) {
        Write-Debug "Script Name $LabTechFunction.ps1 matches a defined function. Calling $LabTechFunction $($args|ConvertTo-JSON -Depth 1 -Compress)"
        & $LabTechFunction @args
    } Else {
        Write-Debug "Script Name $LabTechFunction.ps1 does not match a defined function for this module."
    }
}