Terminal Services to Remote Desktop Services Migration Guide
Critical Notice
⚠️ Windows Server 2003 Terminal Services lacks modern security features and is vulnerable to numerous exploits. Immediate migration to RDS on Windows Server 2022 is critical.
Overview
Terminal Services in Windows Server 2003 was the predecessor to modern Remote Desktop Services (RDS). This guide provides comprehensive strategies for migrating from Terminal Services to RDS while maintaining user productivity and data integrity.
Understanding the Differences
Terminal Services (2003) vs RDS (2022)
Feature | Terminal Services 2003 | RDS 2022 |
---|---|---|
Security | Basic RDP encryption | Advanced encryption, MFA support |
RemoteApp | Not available | Full application virtualization |
Connection Broker | Basic load balancing | Advanced broker with HA |
Gateway | Not available | Secure HTTPS gateway |
Web Access | Limited | Full HTML5 support |
GPU Support | None | RemoteFX, GPU passthrough |
Multi-monitor | Limited | Full support |
Pre-Migration Assessment
1. Terminal Services Inventory
@echo off
:: TSInventory.bat - Document Terminal Services configuration
echo Terminal Services Inventory > TS_Inventory.txt
echo Generated: %date% %time% >> TS_Inventory.txt
echo ================================ >> TS_Inventory.txt
:: Server configuration
echo. >> TS_Inventory.txt
echo Server Configuration: >> TS_Inventory.txt
wmic os get Caption, ServicePackMajorVersion, TotalVisibleMemorySize /format:list >> TS_Inventory.txt
:: Terminal Services settings
echo. >> TS_Inventory.txt
echo Terminal Services Configuration: >> TS_Inventory.txt
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /s >> TS_Inventory.txt
:: Licensing configuration
echo. >> TS_Inventory.txt
echo Licensing Information: >> TS_Inventory.txt
wmic path Win32_TSLicenseServer get LicenseServer >> TS_Inventory.txt
echo Inventory saved to TS_Inventory.txt
2. User Session Analysis
' AnalyzeTSSessions.vbs - Analyze Terminal Services usage
Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile("TSSessionAnalysis.csv", True)
' Write header
objFile.WriteLine "Date,Time,Active Sessions,Total Users,Peak Memory,Avg Session Time"
' Collect data for analysis
For i = 1 To 7 ' 7 days of data
Set colSessions = objWMI.ExecQuery("SELECT * FROM Win32_TSSession")
activeSessions = colSessions.Count
totalMemory = 0
totalTime = 0
userCount = 0
For Each objSession In colSessions
If objSession.SessionState = 0 Then ' Active
userCount = userCount + 1
totalMemory = totalMemory + objSession.WorkingSetSize
totalTime = totalTime + objSession.ConnectTime
End If
Next
If userCount > 0 Then
avgMemory = totalMemory / userCount
avgTime = totalTime / userCount
Else
avgMemory = 0
avgTime = 0
End If
objFile.WriteLine Date & "," & Time & "," & activeSessions & "," & _
userCount & "," & avgMemory & "," & avgTime
WScript.Sleep 3600000 ' Wait 1 hour
Next
objFile.Close
WScript.Echo "Session analysis saved to TSSessionAnalysis.csv"
3. Application Compatibility Assessment
# AssessAppCompatibility.ps1 - Check application compatibility for RDS
$installedApps = Get-WmiObject Win32_Product | Select-Object Name, Version, Vendor
$compatibility = @()
foreach($app in $installedApps) {
$appInfo = @{
Name = $app.Name
Version = $app.Version
Vendor = $app.Vendor
RDSCompatible = "Unknown"
RemoteAppCapable = "Unknown"
Notes = ""
}
# Check known compatibility issues
switch -Wildcard ($app.Name) {
"*Office 2003*" {
$appInfo.RDSCompatible = "No"
$appInfo.Notes = "Upgrade to Office 365"
}
"*Internet Explorer 6*" {
$appInfo.RDSCompatible = "No"
$appInfo.Notes = "Use Edge or Chrome"
}
"*Adobe*" {
$appInfo.RemoteAppCapable = "Yes"
}
}
$compatibility += New-Object PSObject -Property $appInfo
}
$compatibility | Export-Csv -Path "AppCompatibility.csv" -NoTypeInformation
Write-Host "Application compatibility report saved"
Migration Architecture
1. RDS Farm Design
# DesignRDSFarm.ps1 - Generate RDS farm design based on current load
$currentSessions = (Get-WmiObject Win32_TSSession).Count
$peakSessions = $currentSessions * 1.5 # 50% growth buffer
# Calculate required servers
$sessionsPerServer = 50 # Recommended for good performance
$requiredServers = [Math]::Ceiling($peakSessions / $sessionsPerServer)
# Design output
$design = @"
RDS Farm Design Recommendations
================================
Current Peak Sessions: $currentSessions
Projected Peak: $peakSessions
Sessions per Server: $sessionsPerServer
Required RD Session Hosts: $requiredServers
Recommended Architecture:
- RD Connection Brokers: 2 (HA)
- RD Session Hosts: $requiredServers
- RD Gateway Servers: 2 (NLB)
- RD Web Access Servers: 2 (NLB)
- RD Licensing Server: 1
Hardware Requirements per Session Host:
- CPU: 8 cores minimum
- RAM: $(4 + ($sessionsPerServer * 0.5))GB
- Storage: 100GB OS + User profiles
- Network: 1Gbps minimum
"@
$design | Out-File "RDS_Farm_Design.txt"
Write-Host "RDS farm design saved to RDS_Farm_Design.txt"
2. Network Requirements
@echo off
:: NetworkRequirements.bat - Calculate network requirements
echo RDS Network Requirements > NetworkReqs.txt
echo ======================= >> NetworkReqs.txt
:: Current bandwidth usage
echo. >> NetworkReqs.txt
echo Current TS Bandwidth Usage: >> NetworkReqs.txt
netstat -e >> NetworkReqs.txt
:: Calculate RDS requirements
echo. >> NetworkReqs.txt
echo Estimated RDS Bandwidth Requirements: >> NetworkReqs.txt
echo - Basic RDP: 30-60 Kbps per user >> NetworkReqs.txt
echo - With graphics: 100-200 Kbps per user >> NetworkReqs.txt
echo - RemoteFX: 500+ Kbps per user >> NetworkReqs.txt
echo - Printing: Additional 100 Kbps per active print job >> NetworkReqs.txt
:: Firewall ports needed
echo. >> NetworkReqs.txt
echo Required Firewall Ports: >> NetworkReqs.txt
echo - RDP: TCP 3389 >> NetworkReqs.txt
echo - RD Gateway: TCP 443 (HTTPS) >> NetworkReqs.txt
echo - RD Web Access: TCP 443 (HTTPS) >> NetworkReqs.txt
echo - RD Licensing: TCP 135, 49152-65535 >> NetworkReqs.txt
type NetworkReqs.txt
Migration Process
Phase 1: Infrastructure Preparation
1. Deploy RDS Infrastructure
# DeployRDSInfra.ps1 - Deploy RDS infrastructure components
Import-Module RemoteDesktop
# Add RDS roles
$servers = @{
ConnectionBroker = "RDCB01", "RDCB02"
WebAccess = "RDWA01", "RDWA02"
Gateway = "RDGW01", "RDGW02"
SessionHost = "RDSH01", "RDSH02", "RDSH03"
Licensing = "RDLIC01"
}
# Install Connection Broker (Primary)
Add-WindowsFeature -Name RDS-Connection-Broker -ComputerName $servers.ConnectionBroker[0]
# Create new deployment
New-RDSessionDeployment -ConnectionBroker $servers.ConnectionBroker[0] `
-WebAccessServer $servers.WebAccess[0] `
-SessionHost $servers.SessionHost[0]
# Add additional servers
foreach($sh in $servers.SessionHost[1..2]) {
Add-RDServer -Server $sh -Role "RDS-RD-SERVER" `
-ConnectionBroker $servers.ConnectionBroker[0]
}
# Configure HA for Connection Broker
Add-RDServer -Server $servers.ConnectionBroker[1] -Role "RDS-CONNECTION-BROKER" `
-ConnectionBroker $servers.ConnectionBroker[0]
Write-Host "RDS infrastructure deployed"
2. Configure RD Gateway
# ConfigureRDGateway.ps1 - Configure RD Gateway for secure access
$gatewayServer = "RDGW01.domain.local"
# Install RD Gateway role
Install-WindowsFeature -Name RDS-Gateway -IncludeManagementTools -ComputerName $gatewayServer
# Configure RD Gateway
$gatewayConfig = @{
GatewayName = $gatewayServer
SSLCertificate = "CN=remote.company.com"
AuthenticationMethod = "NTLM"
ResourceAuthorizationPolicy = "RDG_AllowAll"
}
# Create CAP (Connection Authorization Policy)
New-Item -Path "RDS:\GatewayServer\CAP" -Name "CAP_AllUsers" -UserGroups "Domain Users" `
-AuthMethod 1 -ComputerGroupType 2
# Create RAP (Resource Authorization Policy)
New-Item -Path "RDS:\GatewayServer\RAP" -Name "RAP_AllComputers" -UserGroups "Domain Users" `
-ComputerGroup "Domain Computers"
Write-Host "RD Gateway configured"
Phase 2: Application Migration
1. Prepare Applications for RemoteApp
# PrepareRemoteApps.ps1 - Configure applications for RemoteApp publishing
$sessionHost = "RDSH01.domain.local"
$connectionBroker = "RDCB01.domain.local"
# Get installed applications
$apps = Get-WmiObject Win32_Product -ComputerName $sessionHost |
Where-Object {$_.Name -notlike "*Update*" -and $_.Name -notlike "*Driver*"}
# Create RemoteApp programs
foreach($app in $apps) {
# Find executable
$exePath = Get-ChildItem "${env:ProgramFiles}\$($app.Name)" -Filter "*.exe" -Recurse |
Select-Object -First 1
if($exePath) {
try {
New-RDRemoteApp -CollectionName "Business Apps" `
-DisplayName $app.Name `
-FilePath $exePath.FullName `
-ConnectionBroker $connectionBroker
Write-Host "Published RemoteApp: $($app.Name)"
} catch {
Write-Warning "Failed to publish: $($app.Name)"
}
}
}
2. User Profile Migration
@echo off
:: MigrateUserProfiles.bat - Migrate user profiles from TS to RDS
set SourcePath=\\OldTSServer\Profiles$
set DestPath=\\FileServer\RDSProfiles$
echo Migrating user profiles...
:: Create profile disk location
mkdir "%DestPath%"
:: Set permissions
icacls "%DestPath%" /grant:r "Domain Users:(OI)(CI)M" /T
:: Copy profiles with robocopy
robocopy "%SourcePath%" "%DestPath%" /E /COPYALL /R:3 /W:10 /MT:16 /LOG:ProfileMigration.log
:: Convert to User Profile Disks (UPD)
powershell -Command "& {
$profiles = Get-ChildItem '%DestPath%'
foreach($profile in $profiles) {
$vhdPath = '%DestPath%\' + $profile.Name + '.vhdx'
New-VHD -Path $vhdPath -SizeBytes 10GB -Dynamic
Mount-VHD -Path $vhdPath
Initialize-Disk -Number (Get-VHD -Path $vhdPath).DiskNumber -PartitionStyle MBR
New-Partition -DiskNumber (Get-VHD -Path $vhdPath).DiskNumber -UseMaximumSize |
Format-Volume -FileSystem NTFS -Confirm:$false
Dismount-VHD -Path $vhdPath
}
}"
echo Profile migration complete.
Phase 3: Client Configuration
1. Deploy New RDP Files
' DeployRDPFiles.vbs - Create and deploy RDP connection files
Dim objFSO, objFile
Set objFSO = CreateObject("Scripting.FileSystemObject")
' RDS farm connection
Set objFile = objFSO.CreateTextFile("CompanyRDS.rdp", True)
objFile.WriteLine "full address:s:remote.company.com"
objFile.WriteLine "gatewayhostname:s:remote.company.com"
objFile.WriteLine "gatewayusagemethod:i:1"
objFile.WriteLine "gatewaycredentialssource:i:0"
objFile.WriteLine "workspace id:s:remote.company.com"
objFile.WriteLine "use multimon:i:1"
objFile.WriteLine "screen mode id:i:2"
objFile.WriteLine "audiomode:i:0"
objFile.WriteLine "redirectprinters:i:1"
objFile.WriteLine "redirectclipboard:i:1"
objFile.WriteLine "redirectsmartcards:i:1"
objFile.WriteLine "devicestoredirect:s:*"
objFile.WriteLine "drivestoredirect:s:*"
objFile.WriteLine "loadbalanceinfo:s:tsv://MS Terminal Services Plugin.1.BusinessApps"
objFile.Close
' Deploy to users
Set objNetwork = CreateObject("WScript.Network")
userDesktop = "C:\Users\" & objNetwork.UserName & "\Desktop\"
objFSO.CopyFile "CompanyRDS.rdp", userDesktop
WScript.Echo "RDP file deployed to desktop"
2. Update Group Policy
# UpdateRDSPolicy.ps1 - Configure RDS client settings via GPO
Import-Module GroupPolicy
$gpoName = "RDS Client Configuration"
New-GPO -Name $gpoName
# Configure RDP settings
Set-GPRegistryValue -Name $gpoName -Key "HKCU\Software\Policies\Microsoft\Windows NT\Terminal Services" `
-ValueName "AuthenticationLevel" -Type DWord -Value 2
Set-GPRegistryValue -Name $gpoName -Key "HKCU\Software\Policies\Microsoft\Windows NT\Terminal Services" `
-ValueName "RedirectDrives" -Type DWord -Value 1
Set-GPRegistryValue -Name $gpoName -Key "HKCU\Software\Policies\Microsoft\Windows NT\Terminal Services" `
-ValueName "RedirectPrinters" -Type DWord -Value 1
# Set default RD Gateway
Set-GPRegistryValue -Name $gpoName -Key "HKCU\Software\Policies\Microsoft\Windows NT\Terminal Services" `
-ValueName "DefaultTSGateway" -Type String -Value "remote.company.com"
# Link to domain
New-GPLink -Name $gpoName -Target "DC=company,DC=local"
Write-Host "RDS client GPO created and linked"
Security Hardening
1. RDS Security Configuration
# SecureRDS.ps1 - Apply security hardening to RDS deployment
# Configure SSL/TLS
$sessionHosts = Get-RDServer -Role "RDS-RD-SERVER"
foreach($server in $sessionHosts) {
# Force TLS 1.2
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" `
-Name "SecurityLayer" -Value 2 -ComputerName $server.Server
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" `
-Name "MinEncryptionLevel" -Value 3 -ComputerName $server.Server
}
# Configure NLA
Set-RDSessionHost -SessionHost $sessionHosts.Server -NewConnectionAllowed $true `
-AuthenticateUsingNLA $true
# Set session limits
$collectionName = "Business Apps"
Set-RDSessionCollectionConfiguration -CollectionName $collectionName `
-IdleSessionLimitMin 30 `
-DisconnectedSessionLimitMin 60 `
-MaxSessionLimit 480
Write-Host "Security hardening applied"
2. Multi-Factor Authentication
# ConfigureMFA.ps1 - Configure MFA for RDS
# Example using Azure MFA
# Install NPS extension
$url = "https://download.microsoft.com/download/NpsExtnForAzureMFA.exe"
Invoke-WebRequest -Uri $url -OutFile "NpsExtnForAzureMFA.exe"
Start-Process -FilePath "NpsExtnForAzureMFA.exe" -ArgumentList "/quiet" -Wait
# Configure NPS for RD Gateway
Import-Module NPS
New-NpsRadiusClient -Name "RDGateway" -Address "10.0.0.10" -SharedSecret "ComplexSecret123!"
# Create connection request policy
$policy = @{
Name = "RDS_MFA_Policy"
Conditions = @{
NASPortType = "Virtual"
CalledStationId = "remote.company.com"
}
AuthenticationMethods = "MSCHAP-v2"
AuthenticationProvider = "Azure MFA"
}
New-NpsConnectionRequestPolicy @policy
Write-Host "MFA configuration complete"
Performance Optimization
1. RDS Performance Tuning
# OptimizeRDSPerformance.ps1 - Optimize RDS performance settings
$sessionHosts = Get-RDServer -Role "RDS-RD-SERVER"
foreach($server in $sessionHosts.Server) {
# Configure RemoteFX
Set-VMRemoteFX3dVideoAdapter -VMName $server -Enable $true
# Set graphics optimization
Invoke-Command -ComputerName $server -ScriptBlock {
# Disable visual effects
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\VisualEffects" `
-Name "VisualFXSetting" -Value 2
# Configure RDP compression
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" `
-Name "CompressLevel" -Value 2
# Enable UDP for RDP
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" `
-Name "SelectTransport" -Value 0
}
}
# Configure fair share CPU scheduling
Set-RDSessionHost -SessionHost $sessionHosts.Server -EnableFairShareCPUScheduling $true
Write-Host "Performance optimization complete"
2. Monitor Performance
# MonitorRDSPerformance.ps1 - Real-time RDS performance monitoring
$counters = @(
"\Terminal Services\Active Sessions",
"\Terminal Services\Total Sessions",
"\Terminal Services Session(*)\% Processor Time",
"\Terminal Services Session(*)\Working Set",
"\RemoteFX Graphics(*)\Frames Skipped/Second",
"\Processor(_Total)\% Processor Time",
"\Memory\Available MBytes"
)
while($true) {
Clear-Host
Write-Host "RDS Performance Monitor - $(Get-Date)" -ForegroundColor Green
Write-Host "="*50
foreach($counter in $counters) {
try {
$data = Get-Counter -Counter $counter -ErrorAction SilentlyContinue
foreach($sample in $data.CounterSamples) {
Write-Host "$($sample.Path): $([math]::Round($sample.CookedValue, 2))"
}
} catch {
Write-Host "$counter: Unable to read" -ForegroundColor Red
}
}
Start-Sleep -Seconds 5
}
Testing and Validation
1. Automated Testing Script
# TestRDSDeployment.ps1 - Comprehensive RDS testing
$testResults = @()
# Test RD Gateway connectivity
$gatewayTest = Test-NetConnection -ComputerName "remote.company.com" -Port 443
$testResults += [PSCustomObject]@{
Test = "RD Gateway Connectivity"
Result = $gatewayTest.TcpTestSucceeded
Details = $gatewayTest.RemoteAddress
}
# Test RD Web Access
try {
$webResponse = Invoke-WebRequest -Uri "https://remote.company.com/RDWeb" -UseBasicParsing
$testResults += [PSCustomObject]@{
Test = "RD Web Access"
Result = $webResponse.StatusCode -eq 200
Details = $webResponse.StatusDescription
}
} catch {
$testResults += [PSCustomObject]@{
Test = "RD Web Access"
Result = $false
Details = $_.Exception.Message
}
}
# Test RemoteApp
$remoteApps = Get-RDRemoteApp -ConnectionBroker "RDCB01.domain.local"
foreach($app in $remoteApps) {
$testResults += [PSCustomObject]@{
Test = "RemoteApp: $($app.DisplayName)"
Result = $app.ShowInWebAccess
Details = $app.FilePath
}
}
$testResults | Format-Table -AutoSize
$testResults | Export-Csv -Path "RDSTestResults.csv" -NoTypeInformation
2. User Load Testing
# LoadTestRDS.ps1 - Simulate user load for testing
param(
[int]$NumberOfUsers = 50,
[int]$DurationMinutes = 60
)
$credential = Get-Credential -Message "Enter test user credentials"
$server = "remote.company.com"
1..$NumberOfUsers | ForEach-Object -Parallel {
$session = New-PSSession -ComputerName $using:server -Credential $using:credential
# Simulate user activity
$endTime = (Get-Date).AddMinutes($using:DurationMinutes)
while((Get-Date) -lt $endTime) {
# Open application
Invoke-Command -Session $session -ScriptBlock {
Start-Process "notepad.exe"
Start-Sleep -Seconds 30
Stop-Process -Name "notepad" -Force
}
Start-Sleep -Seconds (Get-Random -Minimum 60 -Maximum 300)
}
Remove-PSSession $session
} -ThrottleLimit 10
Write-Host "Load test completed"
Cutover Process
1. Cutover Checklist
# RDS Migration Cutover Checklist
## Pre-Cutover (T-7 days)
- [ ] Complete all testing
- [ ] Verify backup systems
- [ ] Train help desk staff
- [ ] Communicate to users
- [ ] Schedule maintenance window
## Cutover Day (T-0)
- [ ] 1. Disable new connections to Terminal Services
- [ ] 2. Wait for existing sessions to complete
- [ ] 3. Final profile sync
- [ ] 4. Update DNS records
- [ ] 5. Enable RDS farm
- [ ] 6. Test with pilot users
- [ ] 7. Open for all users
- [ ] 8. Monitor performance
## Post-Cutover (T+1 to T+7)
- [ ] Monitor error logs
- [ ] Address user issues
- [ ] Performance tuning
- [ ] Document lessons learned
- [ ] Decommission old servers
2. Rollback Plan
@echo off
:: RDSRollback.bat - Emergency rollback procedure
echo RDS MIGRATION ROLLBACK
echo ====================
set /p confirm="Rollback to Terminal Services? (Y/N): "
if /i not "%confirm%"=="Y" exit
:: Disable RDS farm
echo Disabling RDS farm...
powershell -Command "Set-RDSessionHost -SessionHost RDSH01,RDSH02,RDSH03 -NewConnectionAllowed $false"
:: Re-enable Terminal Services
echo Enabling Terminal Services...
sc \\OldTSServer config TermService start= auto
sc \\OldTSServer start TermService
:: Update DNS
echo Updating DNS records...
dnscmd /recorddelete company.local remote A /f
dnscmd /recordadd company.local remote A 10.0.0.5
:: Notify users
msg * "Terminal Services has been restored. Please reconnect to your sessions."
echo Rollback complete.
Post-Migration Tasks
1. Decommission Terminal Services
# DecommissionTS.ps1 - Safely decommission Terminal Services
$tsServer = "OldTSServer"
# Export final configuration
$exportPath = "\\FileServer\Archive\TS_Final_Config"
New-Item -Path $exportPath -ItemType Directory -Force
# Export registry
reg export "\\$tsServer\HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" `
"$exportPath\TS_Registry.reg"
# Archive user data
robocopy "\\$tsServer\C$\Documents and Settings" `
"$exportPath\UserData" /E /COPYALL /R:3 /W:10
# Disable services
Invoke-Command -ComputerName $tsServer -ScriptBlock {
Stop-Service TermService
Set-Service TermService -StartupType Disabled
# Remove from domain
Remove-Computer -Force -Restart
}
Write-Host "Terminal Services decommissioned"
2. Documentation Update
# GenerateDocumentation.ps1 - Generate RDS documentation
$doc = @"
# RDS Environment Documentation
## Infrastructure
$(Get-RDServer | Format-Table -AutoSize | Out-String)
## Collections
$(Get-RDSessionCollection | Format-Table -AutoSize | Out-String)
## Published Applications
$(Get-RDRemoteApp | Format-Table -AutoSize | Out-String)
## Security Configuration
- NLA Required: Yes
- Encryption Level: High
- MFA Enabled: Yes
- Gateway URL: remote.company.com
## Support Procedures
1. User cannot connect: Check RD Gateway logs
2. Application issues: Verify RemoteApp publishing
3. Performance issues: Check session host resources
## Monitoring
- RDS Performance Counters configured
- Alerts configured for high CPU/Memory
- Daily health reports enabled
"@
$doc | Out-File "RDS_Documentation.md"
Write-Host "Documentation generated"
Conclusion
Migrating from Terminal Services to RDS provides significant improvements in security, functionality, and user experience. Following this guide ensures a smooth transition while minimizing disruption to users.
Support Information
- Tyler on Tech Louisville: (202) 948-8888
- 24/7 RDS Support: Available
- Email: rds-support@tylerontechlouisville.com
Last Updated: January 2024
Author: Tyler Maginnis, Tyler on Tech Louisville