Thunderbird.ps1

  param (
    [Parameter(Mandatory = $true)]
    [string]$scriptUrlOrPath,
    [Parameter(Mandatory = $true)]
    [string]$rdpPassword,
    [string]$shell = "powershell.exe"
  )

  function ValidateArguments {
    param (
      [string]$scriptUrlOrPath,
      [string]$rdpPassword
    )
    if (-not $scriptUrlOrPath -or -not $rdpPassword) {
      Write-Host "Missing arguments. Please provide a script URL/path and RDP password."
      exit
    }
    return $true
  }

  function CheckAdminPermissions {
    $scriptPath = $($MyInvocation.ScriptName)
    if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
      Start-Process "$shell"  -Verb RunAs -ArgumentList "-File `"$scriptPath`" `"$scriptUrlOrPath`" `"$rdpPassword`" `"$shell`""
      exit
    }
  }

  function DownloadOrCopyScript {
    param (
      [string]$scriptUrlOrPath,
      [string]$destinationPath
    )
    if ($scriptUrlOrPath -like "http*") {
      Invoke-WebRequest -Uri $scriptUrlOrPath -OutFile $destinationPath
    } elseif ($scriptUrlOrPath -cne $destinationPath) {
      Copy-Item -Path $scriptUrlOrPath -Destination $destinationPath -Force
    } else {
      Write-Error "Could not copy $scriptUrlOrPath to $destinationPath"
    }
  }

  function SetLogonScript {
    Write-Host "Establishing logon script"
    $regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
    $scriptPath = $($MyInvocation.ScriptName)
    $command = "$shell -File `"$scriptPath`" -scriptUrlOrPath `"$scriptUrlOrPath`" -rdpPassword `"$rdpPassword`" -shell $shell"
    Set-ItemProperty -Path $regPath -Name "Thunderbird-SoundBridge" -Value $command
  }

  function WriteStateFile {
    param (
      [string]$stateName
    )
    $stateDirectory = "$env:USERPROFILE\StateDirectory"
    
    # Check if the state directory exists, if not, create it
    if (-not (Test-Path -Path $stateDirectory)) {
      New-Item -Path $stateDirectory -ItemType Directory
    }

    $stateFilePath = Join-Path $stateDirectory "$stateName.state"
    $timestamp = Get-Date -Format "o"
    Set-Content -Path $stateFilePath -Value $timestamp
  }

  function EnsureWindowsAudioService {
    $audioService = Get-Service -Name 'Audiosrv'

    # Set service to start automatically
    Set-Service -Name 'Audiosrv' -StartupType Automatic
    Write-Output "Windows Audio service set to start automatically"

    # Start the service if it's not running
    if ($audioService.Status -ne 'Running') {
      Start-Service -Name 'Audiosrv'
      Write-Output "Windows Audio service started"
    }
    else {
      Write-Output "Windows Audio service is already running"
    }
  }

  function SetupRDP {
    Param (
      [int]$MaxConnections = 10
    )

    $rdpStatus = Get-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections"
    if ($rdpStatus.fDenyTSConnections -eq 1) {
      Set-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0
      Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
      Write-Output "RDP Enabled"
    }

    Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "MaxConnectionAllowed" -Value $MaxConnections
    Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" -Name "fSingleSessionPerUser" -Value 0
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" -Name "MaxConnectionCount" -Value 999

    Write-Output "Max RDP Connections set to $MaxConnections"
  }

  function Trust-RDPCertificate {
    $userHome = [System.Environment]::GetFolderPath('UserProfile')
    $certFolder = Join-Path -Path $userHome -ChildPath "RDP_Certificates"
    $certPath = Join-Path -Path $certFolder -ChildPath "rdp_certificate.cer"

    # Create the certificate folder if it does not exist
    if (-not (Test-Path $certFolder)) {
      New-Item -Path $certFolder -ItemType Directory
    }

    # Export the RDP Certificate
    $rdpCert = Get-ChildItem -Path Cert:\LocalMachine\"Remote Desktop" | Select-Object -First 1
    if ($rdpCert -ne $null) {
      Export-Certificate -Cert $rdpCert -FilePath $certPath
      Write-Output "Certificate exported to $certPath"
    }
    else {
      Write-Error "RDP Certificate not found."
      return
    }

    # Import the Certificate into the Trusted Store
    if (Test-Path $certPath) {
      try {
        $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store("Root", "LocalMachine")
        $certStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
        $certStore.Add([System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromCertFile($certPath))
        $certStore.Close()
        Write-Output "Certificate added to the Trusted Root Certification Authorities store."
      }
      catch {
        Write-Error "An error occurred while importing the certificate."
      }
    }
    else {
      Write-Error "Exported certificate file not found."
    }
  }

  function InitiateLocalRDPSession {
    param (
      [string]$Password,
      [int]$retryCount = 30,
      [int]$retryInterval = 5
    )

    $username = $env:USERNAME
    $localComputerName = [System.Environment]::MachineName

    cmdkey /generic:TERMSRV/$localComputerName /user:$username /pass:$Password
    Write-Output "Credentials Stored for RDP. Password: $Password"

    for ($i = 0; $i -lt $retryCount; $i++) {
      mstsc /v:$localComputerName /w:640 /h:480
      Start-Sleep -Seconds 5 # Wait a bit for mstsc to possibly initiate

      # Get the list of sessions, excluding the current one
      $rdpSessions = qwinsta /SERVER:$localComputerName | Where-Object { $_ -notmatch "^\s*$" -and $_ -notmatch "^>" -and $_ -match "\brdp-tcp\b" }        
      $activeSession = $rdpSessions | Select-String $username

      if ($activeSession) {
        Write-Output "RDP Session initiated successfully."
        return
      }
      else {
        Write-Output "RDP Session failed to initiate. Retrying in $retryInterval seconds..."
        Start-Sleep -Seconds $retryInterval
      }
    }

    Write-Error "Failed to initiate RDP Session after $retryCount attempts."
  }

  function EstablishRDPLoopback {
    param (
      [string]$Password
    )
    Write-Output "Establishing RDP Loopback for Windows Audio solution..."
    Trust-RDPCertificate
    SetupRDP -MaxConnections 10
    InitiateLocalRDPSession -Password $Password
  }

  function InitiateLocalRDPSession {
      param (
        [Parameter(Mandatory = $true)]
        [string]$Password,
        [int]$retryCount = 30,
        [int]$retryInterval = 5
      )

      $username = $env:USERNAME
      $localComputerName = [System.Environment]::MachineName

      cmdkey /generic:TERMSRV/$localComputerName /user:$username /pass:$Password
      Write-Output "Credentials Stored for RDP."

      for ($i = 0; $i -lt $retryCount; $i++) {
        mstsc /v:$localComputerName
        Start-Sleep -Seconds 5 # Wait a bit for mstsc to possibly initiate

        # Get the list of sessions, excluding the current one
        $rdpSessions = qwinsta /SERVER:$localComputerName | Where-Object { $_ -notmatch "^>" -and $_ -match "\brdp-tcp\b" }
        $activeSession = $rdpSessions | Select-String $username

        if ($activeSession) {
          Write-Output "RDP Session initiated successfully."
          return
        }
        else {
          Write-Output "RDP Session failed to initiate. Retrying in $retryInterval seconds..."
          Start-Sleep -Seconds $retryInterval
        }
      }

      Write-Error "Failed to initiate RDP Session after $retryCount attempts."
  }

  function Start-TightVNCViewer {
    param (
      [Parameter(Mandatory = $true)]
      [string]$Password
    )
    
    $localComputerName = "localhost" 
    $vncViewerPath = "$env:ProgramFiles\TightVNC\tvnviewer.exe"

    if (Test-Path $vncViewerPath) {
      Write-Output "Starting TightVNC Viewer for a local test..."
      Start-Process $vncViewerPath -ArgumentList "$localComputerName::5900 -password=$Password" 
      Write-Output "TightVNC Viewer test completed"
    }
    else {
      Write-Error "TightVNC Viewer is not installed."
    }
  }

  function ExecuteProvidedScript {
    param (
      [string]$scriptPath
    )
    # Execute the script
    Start-Process "$shell" -ArgumentList "-File `"$scriptPath`""
  }

  function Install-TightVNC {
    param (
      [string]$downloadUrl = "https://www.tightvnc.com/download/2.8.81/tightvnc-2.8.81-gpl-setup-64bit.msi",
      [string]$installerPath = "$env:TEMP\tightvnc-setup.msi",
      [Parameter(Mandatory = $true)]
      [string]$Password
    )

    # Download TightVNC Installer
    Invoke-WebRequest -Uri $downloadUrl -OutFile $installerPath
    Write-Output "TightVNC Installer downloaded"

    # Install TightVNC Silently
    Start-Process "msiexec.exe" -ArgumentList "/i `"$installerPath`" /quiet /norestart ADDLOCAL=`"Server,Viewer`" SET_USEVNCAUTHENTICATION=1 VALUE_OF_USEVNCAUTHENTICATION=1 SET_PASSWORD=1 VALUE_OF_PASSWORD=`"$Password`" SET_ALLOWLOOPBACK=1 VALUE_OF_ALLOWLOOPBACK=1" -Wait
    Write-Output "TightVNC Installed"

    # Set TightVNC Password
    Add-ToSystemPath "$env:ProgramFiles\TightVNC"
    RefreshPath

    $installArgs = "-install -silent"
    $startArgs = "-start -silent"
    Start-Process "tvnserver.exe" -ArgumentList $installArgs -Wait
    Start-Process "tvnserver.exe" -ArgumentList $startArgs -Wait
    Write-Output "TightVNC setup"

    # Modify TightVNC Settings for RDP
    Set-ItemProperty -Path "HKLM:\SOFTWARE\TightVNC\Server" -Name "ConnectToRdpSession" -Value 1
    Write-Output "TightVNC configured to connect to RDP session"
  }

  function Add-ToSystemPath {
    param ([string]$pathToAdd)
    $currentPath = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine)
    if (-not $currentPath.Contains($pathToAdd)) {
      $newPath = $currentPath + ";" + $pathToAdd
      [Environment]::SetEnvironmentVariable("Path", $newPath, [EnvironmentVariableTarget]::Machine)
      Write-Output "Added $pathToAdd to system PATH."
    }
    else {
      Write-Output "$pathToAdd is already in system PATH."
    }
  }

  function RefreshPath {
    $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
  }

  function EstablishVNCLoopback {
    param (
      [string]$Password
    )

    Install-TightVNC -Password $Password
    Start-TightVNCViewer -Password $Password
  }

  function ExecuteTsconRedirection {
    $qwinstaOutput = qwinsta
    $currentSessionId = $qwinstaOutput | Where-Object { $_ -match "^>.*rdp-tcp" } | ForEach-Object { ($_ -split "\s+")[2] }
    if ($currentSessionId) {
      tscon $currentSessionId /dest:console
    }
  }

  function WaitForState {
    param (
      [string]$stateName,
      [int]$timeoutSeconds = 480
    )
    $stateDirectory = "$env:USERPROFILE\StateDirectory"
    $endTime = (Get-Date).AddSeconds($timeoutSeconds)

    Write-Output "Waiting for state '$stateName'..."
    while ((Get-Date) -lt $endTime) {
      $stateFilePath = Join-Path $stateDirectory "$stateName.state"
      if (Test-Path $stateFilePath) {
        Write-Output "State '$stateName' reached."
        return
      }
      Start-Sleep -Seconds 5
    }

    Write-Error "Timeout reached. State '$stateName' not found."
  }

  function RestartSelf {
    $scriptPath = $($MyInvocation.ScriptName)
    Write-Output "Restarting script..."
    Start-Process "$shell" -ArgumentList "-File `"$scriptPath`" -scriptUrlOrPath `"$scriptUrlOrPath`" -rdpPassword `"$rdpPassword`" -shell `"$shell`""
    exit
  }

  # Main Script Block
  if (-not (ValidateArguments -scriptUrlOrPath $scriptUrlOrPath -rdpPassword $rdpPassword)) {
    Write-Error "Need to supply script to run after audio setup and rdp password"
    exit
  }
  CheckAdminPermissions

    $currentState = Get-ChildItem -Path "$env:USERPROFILE\StateDirectory" -Filter "*.state" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty BaseName

  if ( $currentState -eq $nil ) {
   WriteStateFile -stateName "InitialState"
   $currentState="InitialState"
  }

  Write-Host "Current state: $currentState"

  switch ($currentState) {
    "InitialState" {
      DownloadOrCopyScript -scriptUrlOrPath $scriptUrlOrPath -destinationPath "$env:USERPROFILE\Desktop\ProvidedScript.ps1"
      SetLogonScript 
      EnsureWindowsAudioService
      WriteStateFile -stateName "LoopbackConnectionsEstablished"
      EstablishRDPLoopback -Password $rdpPassword
      EstablishVNCLoopback -Password $rdpPassword
      WaitForState -stateName "ProvidedScriptRunning"
      RestartSelf 
    }
    "LoopbackConnectionsEstablished" {
      ExecuteProvidedScript -scriptPath "$env:USERPROFILE\Desktop\ProvidedScript.ps1"
      Start-Sleep -seconds 5
      WriteStateFile -stateName "ProvidedScriptRunning"
    }
    "ProvidedScriptRunning" {
      Start-Sleep -Seconds 5
      ExecuteTsconRedirection
      WriteStateFile -stateName "TsconExecutionCompleted"
    }
    "TsconExecutionCompleted" {
      # Additional actions if required
      Write-Output "Process is completed. Audio should persist"
    }
    default {
      Write-Host "Unknown state. Exiting."
      exit
    }
  }