Session Launchers

One double-click starts your wheel software, OpenKneeboard, RaceVisor, your sim and OBS — in the order that actually works. Every script is shown in full below so you can read it before you download it, and the generator writes one for your own hardware.

Download

Everything on this page in one folder: five sim launchers, the script they share, two VR helpers and the README.

These are plain text files. They install nothing, and the only setting they change is the active OpenXR runtime, which Set_XR_Runtime.ps1 changes back. Windows 10 / 11 only.

Starting points, not guarantees. They were written for one rig and then made general. Every setup is different, so expect to fix a path or two. The launcher tells you what it could not find.

Quick start

  1. Before unzipping, right-click the .zip → Properties → tick Unblock → OK. This stops Windows warning about each script.
  2. Unzip the folder anywhere, for example Documents\RaceVisor-Launchers. Keep all the files together.
  3. Optional: if you use RaceVisor, put RaceVisor.exe in the same folder or set its path in the next step. The launchers work without it — it is simply skipped.
  4. Open Start_Sim.ps1 in Notepad and look at the block marked CONFIGURE HERE. Remove the apps you do not use and add your own wheel software.
  5. Double-click the .bat for your sim.
  6. Read what it prints. Each line starts with a tag:
TagMeaning
[run ]Started it.
[skip]Already running, or not installed.
[ ok ]Already the way it needs to be.
[warn]Did not do something on purpose. The line says why.
[MISS]Something it needs was not found. The line says what and where it looked.

What the launcher does

Nothing on this list is required. You do not need RaceVisor, a kneeboard or OBS to use the launchers: whatever is not installed is skipped, and the generator lets you untick it.

The order is the whole point. Each step exists because starting things the other way round breaks something:

StepWhatWhy it goes here
1OpenXR runtime, and SteamVR if the runtime points at itWith SteamVR selected but not running, the sim sits at “loading” forever with no error.
2Wheel softwareStarted after the sim, the wheel comes up with default rotation and force feedback.
3OpenKneeboardThe sim only picks up the kneeboard layer if it is already there.
4RaceVisorStarted after a fullscreen sim, the overlay can knock the sim out of its display mode.
5The simSteam games go through Steam, so Steam starts itself if it is closed.
6OBS, lastStarted before the sim is rendering, the capture latches a tiny placeholder and records a blurry picture.

Script generator

Pick what you have. The page writes a single self-contained launcher for that combination, with the advice that applies to it. Everything runs in your browser — nothing you type is sent anywhere. The site only counts which menu choices and download buttons get used, so we know which sims and hardware to support next.

Sim
Headset
Filled in from the headset. Change it only if you know you need a different one.
Wheel
From Task Manager → Details, without .exe.
Also start
Your PC

Download both and keep them in the same folder. Double-click the .bat to run; it calls the .ps1.

Start_iRacing_Custom.ps1

                
Start_iRacing_Custom.bat

                

The scripts

Each script is printed here exactly as it is in the download. Flags go after the file name, in a shortcut's Target box or at a command prompt.

FileWhat it is
Start_iRacing.batDouble-click launcher for iRacing.
Start_AC.batDouble-click launcher for Assetto Corsa.
Start_ACC.batDouble-click launcher for Assetto Corsa Competizione.
Start_AMS2.batDouble-click launcher for Automobilista 2.
Start_RF2.batDouble-click launcher for rFactor 2.
Start_Sim.ps1The script that does the work. The .bat files call it.
Set_XR_Runtime.ps1Shows or changes the active OpenXR runtime.
Stop_SteamVR.bat / .ps1Stops SteamVR and goes back to the Meta runtime.
README.txtThe instructions on this page, as a text file.

Start_<sim>.bat — the double-click launchers

Five files that differ by one word: the sim name passed to Start_Sim.ps1. They exist so you can double-click, and so you can pin one per sim. Anything you add after the file name is passed straight through, so every flag in the next section works here too:

Start_iRacing.bat -NoFilm

To make plain racing the default, edit the .bat and add -NoFilm after -Sim iRacing on both lines. The .bat uses the full path to PowerShell on purpose: when a .bat is double-clicked, powershell is not always found by name, and the window just flashes and closes.

Start_iRacing.bat · 26 lines Download
@echo off
REM RaceVisor session launcher - iRacing
REM Double-click to run. Anything you add after the file name is passed straight to
REM Start_Sim.ps1, for example:   Start_iRacing.bat -NoFilm
REM
REM To make "plain race, no filming" the default, add -NoFilm after "-Sim iRacing" on
REM both lines below.
REM
REM Full interpreter paths are used on purpose: "powershell" is not always on PATH when
REM a .bat is double-clicked, and then the window just flashes and closes.
set "PS7=C:\Program Files\PowerShell\7\pwsh.exe"
set "PS5=C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
if exist "%PS7%" (
  "%PS7%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0Start_Sim.ps1" -Sim iRacing %*
) else if exist "%PS5%" (
  "%PS5%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0Start_Sim.ps1" -Sim iRacing %*
) else (
  echo Neither PowerShell 7 nor Windows PowerShell found at their standard paths.
  pause
  exit /b 1
)
if not "%errorlevel%"=="0" (
  echo.
  echo   *** The launcher exited with an error - see above. ***
  pause
)

The other four are identical except for the sim name: Start_AC.bat, Start_ACC.bat, Start_AMS2.bat, Start_RF2.bat.

Start_Sim.ps1 — the launcher itself

Starts everything in order for the sim you name. Things that are already running are left alone, so it is safe to run twice.

FlagWhat it does
-Sim <name>Required. One of iRacing, AC, ACC, AMS2, RF2. The .bat files fill this in for you.
-NoFilmPlain race. Does not switch the OpenXR runtime to SteamVR and does not start OBS. Most people want this for everyday driving.
-NoObsDo the VR and runtime side, but leave OBS alone.
-ForceStart everything even if it looks like it is already running.
-SimWaitSec <n>Seconds to wait for the sim to start rendering before giving up on OBS. Default 600.

With no flags the launcher is in “film mode”: it switches the OpenXR runtime to SteamVR (one administrator prompt), starts SteamVR, and starts OBS once you are in the car. SteamVR's own mirror is the one that shows the kneeboard overlay, which is why filming uses it.

What to edit, all in the CONFIGURE HERE block at the top:

SettingWhat it is
$StartRaceVisor$true or $false. RaceVisor is optional; the launcher works without it.
$RaceVisorExeFull path to RaceVisor.exe. Leave as '' to look next to the script and in the usual folders.
$ObsExeFull path to obs64.exe. Leave as '' to find it automatically.
$PreSimAppsThe apps started before the sim, in order. One line each: a label, the process name, the path, whether to wait for its window, and how long to let it settle. Delete lines you do not use.
$iRacingUiPath to the iRacing launcher. Only change it if iRacing is installed somewhere unusual.
Start_Sim.ps1 · 323 lines Download
# RaceVisor - launch a whole sim-racing session with one double-click.
#
# Starts your wheel software, OpenKneeboard, RaceVisor, the sim and OBS, in the order that
# actually works. Every one of those is optional: anything you do not have is skipped. One script serves every sim: pick the sim with -Sim.
#
#   .\Start_Sim.ps1 -Sim iRacing
#   .\Start_Sim.ps1 -Sim AC
#   .\Start_Sim.ps1 -Sim ACC
#   .\Start_Sim.ps1 -Sim AMS2
#   .\Start_Sim.ps1 -Sim RF2
#
#   -NoFilm          plain race: leave the OpenXR runtime alone and do not start OBS
#   -NoObs           do the VR/runtime side but leave OBS alone
#   -Force           relaunch things even if they look like they are already running
#   -SimWaitSec N    how long to wait for the sim before giving up on OBS (default 600)
#
# Or just double-click one of the Start_<sim>.bat files next to this script.
#
# ORDER MATTERS: wheel software -> OpenKneeboard -> RaceVisor -> the sim -> OBS last.
#   - Wheel software first, or the wheel comes up with default rotation / force feedback.
#   - OpenKneeboard before the sim, or the sim does not pick up the kneeboard layer.
#   - RaceVisor before the sim, so the overlay is already there when the sim goes
#     fullscreen. Starting it AFTER can eject a fullscreen sim from its display mode.
#   - OBS LAST, and only once the sim is really rendering, or an OpenXR mirror source
#     latches a tiny placeholder and the capture is a blurry upscaled mess.
#
# Steam sims are launched through steam://rungameid/<id>, so Steam starts itself if it
# is not running and no clicking around the library is needed.
#
# KEEP THIS FILE ASCII ONLY. Windows PowerShell 5.1 misreads a UTF-8 file that has no
# byte-order mark, and one stray curly quote produces a screen of parse errors.

param(
    [Parameter(Mandatory = $true)]
    [ValidateSet('iRacing', 'AC', 'ACC', 'AMS2', 'RF2')]
    [string]$Sim,

    [switch]$NoObs,
    [switch]$NoFilm,
    [switch]$Force,
    [int]$SimWaitSec = 600
)

# ==============================================================================
#  CONFIGURE HERE - everything you are likely to change is in this block.
#  Leave a path as '' to let the script look in the usual places.
# ==============================================================================

# RaceVisor is OPTIONAL. The launcher works without it: set this to $false, or just leave it -
# if RaceVisor.exe is not found it is skipped.
$StartRaceVisor = $true

# RaceVisor.exe. '' = look next to this script, then the usual install folders.
$RaceVisorExe = ''

# OBS Studio (obs64.exe). '' = ask the registry, then the default install folder.
$ObsExe = ''

# Apps to start BEFORE the sim, in this order. Remove the lines you do not use, or add
# your own. An app that is not installed is skipped quietly.
#   Label        what is printed
#   Process      process name without .exe (used to tell whether it is already running)
#   Exe          full path to the program
#   NeedsWindow  $true = only count it as running once it has a window (tray apps)
#   SettleMs     how long to wait after starting it, in milliseconds
$PreSimApps = @(
    @{ Label = 'Logitech G HUB'; Process = 'lghub';            Exe = 'C:\Program Files\LGHUB\lghub.exe';                          NeedsWindow = $true;  SettleMs = 6000 },
    @{ Label = 'OpenKneeboard';  Process = 'OpenKneeboardApp'; Exe = 'C:\Program Files\OpenKneeboard\bin\OpenKneeboardApp.exe';   NeedsWindow = $false; SettleMs = 2000 }
)

# iRacing has its own launcher instead of a Steam id.
$iRacingUi = 'C:\Program Files (x86)\iRacing\ui\iRacingUI.exe'

# ==============================================================================
#  END OF CONFIGURATION
# ==============================================================================

$Filming = -not $NoFilm

function Find-First([string[]]$candidates) {
    foreach ($c in $candidates) { if ($c -and (Test-Path $c)) { return $c } }
    return $null
}

$dashExe = Find-First @(
    $RaceVisorExe,
    (Join-Path $PSScriptRoot 'RaceVisor.exe'),
    "$env:LOCALAPPDATA\RaceVisor\RaceVisor.exe",
    "$env:ProgramFiles\RaceVisor\RaceVisor.exe",
    "$env:USERPROFILE\Downloads\RaceVisor.exe"
)

# OBS installs wherever the user put it, so ask the registry first.
$obsFromReg = $null
try {
    $k = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\OBS Studio' -ErrorAction Stop
    if ($k.InstallLocation) { $obsFromReg = Join-Path $k.InstallLocation 'bin\64bit\obs64.exe' }
} catch { }
$obsPath = Find-First @(
    $ObsExe,
    $obsFromReg,
    "$env:ProgramFiles\obs-studio\bin\64bit\obs64.exe"
)

# Steam can be installed anywhere and can have several libraries. Find them all.
function Get-SteamLibraries {
    $root = $null
    try { $root = (Get-ItemProperty 'HKCU:\Software\Valve\Steam' -ErrorAction Stop).SteamPath } catch { }
    if ($root) { $root = $root -replace '/', '\' }
    if (-not $root -or -not (Test-Path $root)) { $root = 'C:\Program Files (x86)\Steam' }
    $libs = @($root)
    $vdf = Join-Path $root 'steamapps\libraryfolders.vdf'
    if (Test-Path $vdf) {
        foreach ($l in (Get-Content $vdf | Select-String '"path"')) {
            $lib = ($l -replace '.*"path"\s*"', '' -replace '".*', '') -replace '\\\\', '\'
            if ($lib -and $libs -notcontains $lib) { $libs += $lib }
        }
    }
    return $libs
}

# Per-sim facts. SimProcess is the process that exists ONLY when you are actually
# driving - that is what OBS waits for, not the launcher or the menu.
$sims = @{
    'iRacing' = @{
        Title = 'iRacing'
        SteamId = $null
        Launch = $iRacingUi
        SimProcess = 'iRacingSim64DX11'
        Notes = @()
    }
    'AC' = @{
        Title = 'Assetto Corsa'
        SteamId = '244210'
        Launch = $null
        SimProcess = 'acs'
        Notes = @(
            'VR is set in AC''s OWN launcher: Video -> Rendering Mode -> OpenVR. There is no Steam',
            '  VR dropdown for AC and -vr in Launch Options does nothing.',
            'Video settings must NOT be exclusive fullscreen or the overlay cannot draw over it.')
    }
    'ACC' = @{
        Title = 'Assetto Corsa Competizione'
        SteamId = '805550'
        Launch = $null
        SimProcess = 'AC2-Win64-Shipping'
        Notes = @('Video settings must NOT be exclusive fullscreen or the overlay cannot draw over it.')
    }
    'AMS2' = @{
        Title = 'Automobilista 2'
        SteamId = '1066890'
        # steam://rungameid ALWAYS uses the DEFAULT launch config, which passes -novr and
        # ignores whatever you picked in Steam's dropdown. This picks the VR entry directly.
        SteamVrUrl = 'steam://launch/1066890/vr'
        Launch = $null
        SimProcess = 'AMS2AVX'
        Notes = @(
            'Settings -> System -> Shared Memory must be "Project CARS 2". It is OFF by default',
            '  and with it off RaceVisor cannot tell the game apart from being closed.',
            'Options -> Video -> Windowed or Borderless. In exclusive fullscreen the game gets',
            '  ejected from its display mode and will not restore from the taskbar.')
    }
    'RF2' = @{
        Title = 'rFactor 2'
        SteamId = '365960'
        Launch = $null
        SimProcess = 'rFactor2'
        Notes = @(
            'Needs the rF2 Shared Memory Map plugin enabled in CustomPluginVariables.JSON',
            '  (the key is " Enabled" WITH a leading space, and rF2 rewrites that file on exit,',
            '  so it can only be edited with the game closed).',
            'Set it to bordered/windowed and remember to press APPLY.')
    }
}

$cfg = $sims[$Sim]

Write-Host ""
Write-Host ("  RaceVisor session: {0}" -f $cfg.Title) -ForegroundColor Cyan
Write-Host ("  film mode: {0}" -f $(if ($Filming) { "ON (SteamVR runtime + OBS)" } else { "off" })) -ForegroundColor DarkGray
Write-Host ""

function Test-Up([string]$proc, [bool]$needsWindow = $false) {
    $p = @(Get-Process $proc -ErrorAction SilentlyContinue)
    if ($p.Count -eq 0) { return $false }
    if ($needsWindow) { return (@($p | Where-Object { $_.MainWindowHandle -ne 0 }).Count -gt 0) }
    return $true
}

function Start-Step([string]$label, [string]$proc, [string]$exe, [bool]$needsWindow = $false, [int]$settleMs = 1500) {
    if (-not $Force -and (Test-Up $proc $needsWindow)) {
        Write-Host ("  [skip] {0} already running" -f $label) -ForegroundColor DarkGray
        return
    }
    if (-not $exe -or -not (Test-Path $exe)) {
        Write-Host ("  [skip] {0} not installed ({1})" -f $label, $exe) -ForegroundColor DarkGray
        return
    }
    Write-Host ("  [run ] {0}" -f $label) -ForegroundColor Green
    Start-Process -FilePath $exe -WorkingDirectory (Split-Path $exe) | Out-Null
    Start-Sleep -Milliseconds $settleMs
}

# ---- 1a. If the OpenXR runtime is SteamVR, SteamVR must actually BE RUNNING ----------
# Selecting the runtime is not enough. With it selected but not running, the sim asks for
# a VR session, nothing answers, and it sits at "loading" with no error at all.
$xrRuntime = (Get-ItemProperty 'HKLM:\SOFTWARE\Khronos\OpenXR\1' -ErrorAction SilentlyContinue).ActiveRuntime
if ($xrRuntime -like "*steam*") {
    if (Get-Process vrmonitor -ErrorAction SilentlyContinue) {
        Write-Host "  [ ok ] SteamVR already running" -ForegroundColor Green
    }
    else {
        $vrStart = $null
        foreach ($lib in (Get-SteamLibraries)) {
            $c = Join-Path $lib 'steamapps\common\SteamVR\bin\win64\vrstartup.exe'
            if (Test-Path $c) { $vrStart = $c; break }
        }
        if ($vrStart) {
            Write-Host "  [run ] SteamVR (the OpenXR runtime points at it)" -ForegroundColor Green
            Start-Process $vrStart | Out-Null
            $dl = (Get-Date).AddSeconds(90)
            while ((Get-Date) -lt $dl -and -not (Get-Process vrmonitor -ErrorAction SilentlyContinue)) { Start-Sleep -Seconds 2 }
            Start-Sleep -Seconds 5
        }
        else {
            Write-Host "  [MISS] runtime is SteamVR but SteamVR was not found - the sim will hang at loading" -ForegroundColor Yellow
        }
    }
}

# ---- 1b. film mode: switch the runtime TO SteamVR if it is not already ----
# SteamVR's own mirror is the one that shows the kneeboard overlay, so it is the runtime
# to film with. Skip this whole step with -NoFilm.
if ($Filming) {
    $xr = Join-Path $PSScriptRoot 'Set_XR_Runtime.ps1'
    if (Test-Path $xr) {
        $rt = (Get-ItemProperty 'HKLM:\SOFTWARE\Khronos\OpenXR\1' -ErrorAction SilentlyContinue).ActiveRuntime
        if ($rt -like "*steam*") {
            Write-Host "  [ ok ] OpenXR runtime already SteamVR" -ForegroundColor Green
        }
        elseif (Test-Up $cfg.SimProcess) {
            Write-Host "  [warn] sim is already running - not switching the runtime under it" -ForegroundColor Yellow
        }
        else {
            Write-Host "  [run ] switching OpenXR runtime to SteamVR (admin prompt)" -ForegroundColor Cyan
            & $xr -Steam | Out-Null
        }
    }
    else {
        Write-Host "  [MISS] Set_XR_Runtime.ps1 not found next to this script" -ForegroundColor Yellow
    }
}

# ---- 2. wheel software, kneeboard, overlay - in this order, all BEFORE the sim ----
foreach ($a in $PreSimApps) {
    Start-Step $a.Label $a.Process $a.Exe ([bool]$a.NeedsWindow) ([int]$a.SettleMs)
}
if (-not $StartRaceVisor) { }
elseif ($dashExe) {
    $dashProc = [System.IO.Path]::GetFileNameWithoutExtension($dashExe)
    Start-Step "RaceVisor" $dashProc $dashExe $false 2500
}
else { Write-Host "  [skip] RaceVisor not found (optional - set `$RaceVisorExe at the top of this script to use it)" -ForegroundColor DarkGray }

# ---- 3. the sim ----
if (Test-Up $cfg.SimProcess) {
    Write-Host ("  [skip] {0} already running" -f $cfg.Title) -ForegroundColor DarkGray
}
elseif ($cfg.SteamId) {
    # Prefer the sim's VR launch config when it has one and a headset runtime is live.
    if ($cfg.SteamVrUrl -and (Get-Process vrmonitor -ErrorAction SilentlyContinue)) {
        Write-Host ("  [run ] {0} via Steam - VR launch config" -f $cfg.Title) -ForegroundColor Green
        Start-Process $cfg.SteamVrUrl
    }
    else {
        Write-Host ("  [run ] {0} via Steam (appid {1})" -f $cfg.Title, $cfg.SteamId) -ForegroundColor Green
        Start-Process ("steam://rungameid/{0}" -f $cfg.SteamId)
    }
}
elseif ($cfg.Launch -and (Test-Path $cfg.Launch)) {
    Write-Host ("  [run ] {0}" -f $cfg.Title) -ForegroundColor Green
    Start-Process -FilePath $cfg.Launch | Out-Null
}
else {
    Write-Host ("  [MISS] {0}: launcher not found" -f $cfg.Title) -ForegroundColor Yellow
}

# ---- 4. per-sim reminders, printed while you wait rather than buried in a doc ----
if ($cfg.Notes.Count -gt 0) {
    Write-Host ""
    Write-Host ("  Before you drive - {0}:" -f $cfg.Title) -ForegroundColor Cyan
    foreach ($n in $cfg.Notes) { Write-Host ("    " + $n) -ForegroundColor Gray }
}

# ---- 5. OBS last, and only once the sim is actually rendering ----
if ($Filming -and -not $NoObs) {
    Write-Host ""
    Write-Host ("  Waiting for {0} to start rendering, then launching OBS..." -f $cfg.SimProcess) -ForegroundColor Cyan
    Write-Host "  (get into the car; Ctrl+C if you change your mind)" -ForegroundColor DarkGray
    $deadline = (Get-Date).AddSeconds($SimWaitSec)
    while ((Get-Date) -lt $deadline -and -not (Test-Up $cfg.SimProcess)) { Start-Sleep -Seconds 2 }

    if (Test-Up $cfg.SimProcess) {
        Start-Sleep -Seconds 8   # let the eye textures reach full resolution before OBS latches
        if (-not $Force -and (Test-Up "obs64")) {
            Write-Host "  [skip] OBS already running" -ForegroundColor DarkGray
        }
        elseif ($obsPath) {
            Write-Host "  [run ] OBS Studio" -ForegroundColor Green
            Start-Process -FilePath $obsPath -WorkingDirectory (Split-Path $obsPath) | Out-Null
        }
        else {
            Write-Host "  [skip] OBS Studio not found - no capture" -ForegroundColor DarkGray
        }
    }
    else {
        Write-Host "  Gave up waiting for the sim - OBS not started." -ForegroundColor Yellow
    }
}

Write-Host ""
Write-Host "  Done." -ForegroundColor Green
Start-Sleep -Seconds 4

Set_XR_Runtime.ps1 — which OpenXR runtime is active

Only one OpenXR runtime can be active at a time, and it has to match how the headset is connected. If it does not, the sim opens flat on the monitor with no error. This shows the current one or switches it.

FlagWhat it does
(none)Shows which runtime is active. Changes nothing.
-MetaMeta Quest over the Link cable or Air Link.
-VDVirtual Desktop (wireless).
-SteamSteamVR.

Use one flag only. It asks for administrator permission, because the setting is system-wide, and it refuses to switch while a sim is running. To run it, open PowerShell in the folder:

powershell -NoProfile -ExecutionPolicy Bypass -File .\Set_XR_Runtime.ps1 -Meta

-ExecutionPolicy Bypass applies to that one run only. It does not change your system.

Set_XR_Runtime.ps1 · 100 lines Download
# Show or change the active OpenXR runtime: Meta (Link cable / Air Link), Virtual Desktop
# (VDXR) or SteamVR.
#
# Only ONE OpenXR runtime can be active at a time, and it must match how the headset is
# actually connected. Get it wrong and the sim launches flat on the monitor with no error -
# OpenXR simply has no headset to give it. Virtual Desktop Streamer rewrites this value when
# it runs, so it can silently break cable Link; run this with -Meta to take it back.
#
#   .\Set_XR_Runtime.ps1          just show which runtime is active now
#   .\Set_XR_Runtime.ps1 -Meta    Meta Quest over the Link cable or Air Link
#   .\Set_XR_Runtime.ps1 -VD      Virtual Desktop (wireless)
#   .\Set_XR_Runtime.ps1 -Steam   SteamVR (Index, Vive, Pimax... or filming with a Quest)
#
# Asks for administrator permission by itself - the setting lives in HKEY_LOCAL_MACHINE.

param([switch]$Meta, [switch]$VD, [switch]$Steam)

$key = 'HKLM:\SOFTWARE\Khronos\OpenXR\1'

function Find-First([string[]]$candidates) {
    foreach ($c in $candidates) { if ($c -and (Test-Path -LiteralPath $c)) { return $c } }
    return $null
}

# The Meta PC app was renamed; older installs still live under "Oculus".
$metaCandidates = @(
    'C:\Program Files\Meta Horizon\Support\oculus-runtime\oculus_openxr_64.json',
    'C:\Program Files\Oculus\Support\oculus-runtime\oculus_openxr_64.json'
)
$metaRt = Find-First $metaCandidates
if (-not $metaRt) { $metaRt = $metaCandidates[0] }

$vdRt = 'C:\Program Files\Virtual Desktop Streamer\OpenXR\virtualdesktop-openxr.json'

# SteamVR can sit in any Steam library, so it is located rather than assumed.
function Find-SteamVRRuntime {
    $root = $null
    try { $root = (Get-ItemProperty 'HKCU:\Software\Valve\Steam' -ErrorAction Stop).SteamPath } catch { }
    if ($root) { $root = $root -replace '/', '\' }
    if (-not $root -or -not (Test-Path $root)) { $root = 'C:\Program Files (x86)\Steam' }
    $paths = @($root)
    $vdf = Join-Path $root 'steamapps\libraryfolders.vdf'
    if (Test-Path $vdf) {
        foreach ($line in (Get-Content $vdf | Select-String '"path"')) {
            $lib = ($line -replace '.*"path"\s*"', '' -replace '".*', '') -replace '\\\\', '\'
            if ($lib -and $paths -notcontains $lib) { $paths += $lib }
        }
    }
    foreach ($lib in $paths) {
        $j = Join-Path $lib 'steamapps\common\SteamVR\steamxr_win64.json'
        if (Test-Path -LiteralPath $j) { return $j }
    }
    return (Join-Path $root 'steamapps\common\SteamVR\steamxr_win64.json')
}
$steamRt = Find-SteamVRRuntime

function Show-Current {
    $cur = (Get-ItemProperty $key -ErrorAction SilentlyContinue).ActiveRuntime
    $name = if ($cur -eq $metaRt) { 'META  (Link cable / Air Link)' }
            elseif ($cur -eq $vdRt) { 'VDXR  (Virtual Desktop, wireless)' }
            elseif ($cur -eq $steamRt) { 'STEAMVR  (needs Steam + SteamVR running)' }
            else { 'UNKNOWN' }
    Write-Host "Active OpenXR runtime: $name" -ForegroundColor Cyan
    Write-Host "  $cur" -ForegroundColor DarkGray
}

$picked = @($Meta, $VD, $Steam) | Where-Object { $_ }
if ($picked.Count -gt 1) { Write-Host "Pick one: -Meta, -VD or -Steam." -ForegroundColor Red; exit 1 }
if ($picked.Count -eq 0) { Show-Current; Start-Sleep -Seconds 4; exit 0 }

$target = if ($Meta) { $metaRt } elseif ($Steam) { $steamRt } else { $vdRt }
if (-not (Test-Path -LiteralPath $target)) {
    Write-Host "That runtime is not installed:" -ForegroundColor Red
    Write-Host "  $target" -ForegroundColor Red
    Start-Sleep -Seconds 4; exit 1
}

# Changing runtimes under a live VR app leaves it in a broken half-state.
$simProcs = 'iRacingSim64DX11|acs|AC2-Win64-Shipping|AMS2AVX|rFactor2'
$busy = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match "^($simProcs)$" }
if ($busy) {
    Write-Host "Close these first - switching runtimes mid-session breaks them:" -ForegroundColor Yellow
    $busy | ForEach-Object { Write-Host "  - $($_.ProcessName)" -ForegroundColor Yellow }
    Start-Sleep -Seconds 4; exit 1
}

$s = "Set-ItemProperty -Path '$key' -Name ActiveRuntime -Value '$target'"
Start-Process powershell.exe -Verb RunAs -Wait -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-Command',$s

Start-Sleep -Milliseconds 500
Show-Current
if ($VD) { Write-Host "Remember: this only works through the Virtual Desktop app, not the cable." -ForegroundColor Gray }
elseif ($Steam) {
    Write-Host ""
    Write-Host "Steam AND SteamVR must be running before you start the sim." -ForegroundColor Gray
    Write-Host "Capture: SteamVR menu -> Display VR View, then OBS Window Capture on that window." -ForegroundColor Gray
    Write-Host "Put it back when you are done:  .\Set_XR_Runtime.ps1 -Meta" -ForegroundColor Yellow
}
else { Write-Host "Note: starting Virtual Desktop Streamer will take this back - re-run with -Meta if it does." -ForegroundColor Gray }
Start-Sleep -Seconds 3

Stop_SteamVR.bat and Stop_SteamVR.ps1

SteamVR uses around 1.5 GB of memory while idle. If you only start it to film, this closes it in the right order and sets the OpenXR runtime back to Meta so the Link cable works again. Double-click the .bat, or add a flag after it.

FlagWhat it does
(none)Stops SteamVR and sets the OpenXR runtime back to Meta.
-CloseSteamAlso closes the Steam client.
-KeepRuntimeStops SteamVR but leaves the OpenXR runtime alone. Use this if SteamVR is your normal runtime (Index, Vive, Pimax and so on).
Stop_SteamVR.bat · 14 lines Download
@echo off
REM Double-click: stop SteamVR and put the OpenXR runtime back to Meta.
REM Flags are passed through, for example:   Stop_SteamVR.bat -CloseSteam
set "PS7=C:\Program Files\PowerShell\7\pwsh.exe"
set "PS5=C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
if exist "%PS7%" (
  "%PS7%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0Stop_SteamVR.ps1" %*
) else if exist "%PS5%" (
  "%PS5%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0Stop_SteamVR.ps1" %*
) else (
  echo Neither PowerShell 7 nor Windows PowerShell found at their standard paths.
  pause
  exit /b 1
)
Stop_SteamVR.ps1 · 82 lines Download
# Stop_SteamVR.ps1 - shut SteamVR down and put the OpenXR runtime back to Meta.
#
# SteamVR is heavy: an idle session is around 1.5 GB across vrserver, vrcompositor,
# vrmonitor, vrdashboard and several vrwebhelper processes. If you only use it to film,
# this gets rid of it in the right order and makes the Link cable work again.
#
#   .\Stop_SteamVR.ps1               stop SteamVR, restore the Meta runtime
#   .\Stop_SteamVR.ps1 -CloseSteam   also close the Steam client
#   .\Stop_SteamVR.ps1 -KeepRuntime  stop SteamVR but leave the OpenXR runtime alone
#                                    (use this if SteamVR IS your normal runtime)

param(
    [switch]$CloseSteam,
    [switch]$KeepRuntime
)

Write-Host ""
Write-Host "Shutting SteamVR down..." -ForegroundColor Cyan

# Refuse while a sim is live - pulling the runtime out from under a running sim leaves
# it in a broken half-state rather than closing cleanly.
$simProcs = 'iRacingSim64DX11|acs|AC2-Win64-Shipping|AMS2AVX|rFactor2'
$busy = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match "^($simProcs)$" }
if ($busy) {
    Write-Host "Close these first:" -ForegroundColor Yellow
    $busy | ForEach-Object { Write-Host "  - $($_.ProcessName)" -ForegroundColor Yellow }
    Start-Sleep -Seconds 4
    exit 1
}

$before = @(Get-Process -ErrorAction SilentlyContinue |
            Where-Object { $_.ProcessName -match 'vrserver|vrcompositor|vrmonitor|vrdashboard|vrwebhelper|steamtours' })
$mb = [int](($before | Measure-Object WorkingSet64 -Sum).Sum / 1MB)
Write-Host ("  $($before.Count) SteamVR process(es) using about $mb MB") -ForegroundColor Gray

# vrmonitor first - it is the supervisor, so the rest are not restarted behind our back.
foreach ($name in @('vrmonitor', 'vrdashboard', 'vrwebhelper', 'vrcompositor', 'vrserver', 'steamtours')) {
    $p = Get-Process $name -ErrorAction SilentlyContinue
    if ($p) {
        $p | Stop-Process -Force -ErrorAction SilentlyContinue
        Write-Host "  stopped $name" -ForegroundColor DarkGray
    }
    Start-Sleep -Milliseconds 250
}

Start-Sleep -Seconds 1
$left = @(Get-Process -ErrorAction SilentlyContinue |
          Where-Object { $_.ProcessName -match 'vrserver|vrcompositor|vrmonitor|vrdashboard|vrwebhelper' })
if ($left.Count) {
    Write-Host "  $($left.Count) still running - stopping again..." -ForegroundColor Yellow
    $left | Stop-Process -Force -ErrorAction SilentlyContinue
}

# ---- put the OpenXR runtime back ----
if (-not $KeepRuntime) {
    $meta = $null
    foreach ($c in @(
        'C:\Program Files\Meta Horizon\Support\oculus-runtime\oculus_openxr_64.json',
        'C:\Program Files\Oculus\Support\oculus-runtime\oculus_openxr_64.json')) {
        if (Test-Path -LiteralPath $c) { $meta = $c; break }
    }
    if ($meta) {
        $s = "Set-ItemProperty -Path 'HKLM:\SOFTWARE\Khronos\OpenXR\1' -Name ActiveRuntime -Value '$meta'"
        Start-Process powershell.exe -Verb RunAs -Wait -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-Command',$s
        $now = (Get-ItemProperty 'HKLM:\SOFTWARE\Khronos\OpenXR\1').ActiveRuntime
        if ($now -eq $meta) { Write-Host "  OpenXR runtime restored to Meta (Link cable)." -ForegroundColor Green }
        else { Write-Host "  Runtime NOT restored - run .\Set_XR_Runtime.ps1 -Meta" -ForegroundColor Red }
    } else {
        Write-Host "  Meta runtime not found - leaving the OpenXR runtime as it is." -ForegroundColor Yellow
    }
} else {
    Write-Host "  Runtime left as-is (-KeepRuntime)." -ForegroundColor DarkGray
}

if ($CloseSteam) {
    $st = Get-Process steam -ErrorAction SilentlyContinue
    if ($st) { $st | Stop-Process -Force -ErrorAction SilentlyContinue; Write-Host "  Steam closed." -ForegroundColor Green }
}

Write-Host ""
Write-Host "Done." -ForegroundColor Green
Start-Sleep -Seconds 3

README.txt

The same instructions as this page, for reading offline.

README.txt · 156 lines Download
RACEVISOR SESSION LAUNCHERS
===========================

One double-click starts everything a sim-racing session needs, in the order that works:

    wheel software -> OpenKneeboard -> RaceVisor -> the sim -> OBS (last)

None of those is required. You do not need RaceVisor, a kneeboard or OBS to use the
launchers: whatever is not installed is skipped.

These are plain text scripts. Open any of them in Notepad to see exactly what it does
before you run it. They are starting points: every rig is different, so expect to edit
a path or two. Nothing here installs anything or changes a setting you cannot change back.

Windows 10 / 11 only.


WHAT IS IN THE FOLDER
---------------------

  Start_iRacing.bat    double-click launchers, one per sim
  Start_AC.bat           (Assetto Corsa)
  Start_ACC.bat          (Assetto Corsa Competizione)
  Start_AMS2.bat         (Automobilista 2)
  Start_RF2.bat          (rFactor 2)

  Start_Sim.ps1        the script that does the work; the .bat files just call it
  Set_XR_Runtime.ps1   show or change the active OpenXR runtime (Meta / Virtual Desktop / SteamVR)
  Stop_SteamVR.bat     double-click: stop SteamVR and go back to the Meta runtime
  Stop_SteamVR.ps1     the script behind Stop_SteamVR.bat

Keep all the files together in ONE folder. The .bat files look for the .ps1 files next
to themselves.


QUICK START
-----------

  1. Unzip the folder anywhere, for example Documents\RaceVisor-Launchers.
  2. (Optional) If you use RaceVisor, put RaceVisor.exe in the same folder or set its path
     in step 3. The launchers work without it - it is simply skipped.
  3. Open Start_Sim.ps1 in Notepad and look at the block marked CONFIGURE HERE:
       $StartRaceVisor $true / $false. RaceVisor is optional.
       $RaceVisorExe   where RaceVisor.exe is ('' = look in the usual places)
       $ObsExe         where obs64.exe is     ('' = find it automatically)
       $PreSimApps     the apps started before the sim. Delete the lines you do not
                       use, add your own wheel software, keep the order.
  4. Double-click the .bat for your sim.
  5. Read what it prints. Each line starts with one of:
       [run ]  started it
       [skip]  already running, or not installed
       [ ok ]  already the way it needs to be
       [warn]  did not do something on purpose - the line says why
       [MISS]  something it needs was not found - the line says what

If Windows shows "Windows protected your PC" the first time, that is SmartScreen
reacting to a downloaded script. Right-click the .zip BEFORE unzipping -> Properties ->
tick Unblock -> OK, then unzip again.


FLAGS AND SWITCHES
------------------

Add these after the .bat name in a shortcut or a command prompt, for example:

    Start_iRacing.bat -NoFilm

Start_<sim>.bat / Start_Sim.ps1

  -Sim <name>      which sim: iRacing, AC, ACC, AMS2 or RF2. The .bat files fill this
                   in for you. Required when you run Start_Sim.ps1 directly.
  -NoFilm          plain race. Does NOT switch the OpenXR runtime to SteamVR and does
                   NOT start OBS. Most people want this for everyday driving.
  -NoObs           do the VR/runtime side but leave OBS alone.
  -Force           start everything even if it looks like it is already running.
  -SimWaitSec <n>  how many seconds to wait for the sim to start rendering before
                   giving up on OBS. Default 600 (ten minutes).

  With no flags the launcher is in "film mode": it switches the OpenXR runtime to
  SteamVR (one administrator prompt), starts SteamVR, and starts OBS once you are in
  the car. To make plain racing the default, edit the .bat and add -NoFilm after
  "-Sim <name>" on both lines.

Set_XR_Runtime.ps1

  (no flag)        show which OpenXR runtime is active, change nothing
  -Meta            Meta Quest over the Link cable or Air Link
  -VD              Virtual Desktop (wireless)
  -Steam           SteamVR

  Pick one flag only. Asks for administrator permission. Refuses while a sim is running.

Stop_SteamVR.bat / Stop_SteamVR.ps1

  (no flag)        stop SteamVR and set the OpenXR runtime back to Meta
  -CloseSteam      also close the Steam client
  -KeepRuntime     stop SteamVR but leave the OpenXR runtime alone. Use this if SteamVR
                   is your normal runtime (Index, Vive, Pimax and so on).


RUNNING A .ps1 DIRECTLY
-----------------------

The .bat files handle this for you. To run a .ps1 yourself, open PowerShell in the
folder and use:

    powershell -NoProfile -ExecutionPolicy Bypass -File .\Set_XR_Runtime.ps1

"-ExecutionPolicy Bypass" applies to that one run only. It does not change your system.


CHANGING THINGS
---------------

Different wheel:    edit $PreSimApps in Start_Sim.ps1. Replace the Logitech G HUB line
                    with your wheel software, or delete it if your wheel needs none.
No kneeboard:       delete the OpenKneeboard line from $PreSimApps.
Another sim:        copy one block inside "$sims = @{ ... }" in Start_Sim.ps1, give it a
                    new name, set its Steam app id (or Launch path) and the name of the
                    process that runs while you are driving. Add the name to the
                    ValidateSet list near the top, then copy a .bat and change -Sim.
Not sure of a process name: start the program, open Task Manager -> Details, and use
                    the name in the first column without ".exe".

The web page that goes with these scripts has a generator that writes a script for
your hardware: pick your sim, headset, wheel and extras, and download the result.


IF SOMETHING GOES WRONG
-----------------------

The window flashes and closes
    Open a Command Prompt in the folder and run the .bat from there so the text stays
    on screen.

Red text about "parse errors" or unexpected tokens
    The .ps1 was edited with curly quotes or other special characters. Use straight
    quotes only, and save as plain text.

The sim opens flat on the monitor instead of in the headset
    The active OpenXR runtime does not match how the headset is connected.
    Run Set_XR_Runtime.ps1 with no flags to see it, then -Meta, -VD or -Steam.

The sim sits at "loading" forever in VR
    The OpenXR runtime is SteamVR but SteamVR is not running. Start SteamVR first, or
    switch the runtime back with Set_XR_Runtime.ps1 -Meta.

The overlay does not show over the sim
    The sim is in exclusive fullscreen. Use Windowed or Borderless.

[skip] RaceVisor not found
    Not an error - RaceVisor is optional. To use it, set $RaceVisorExe at the top of
    Start_Sim.ps1 to the full path of RaceVisor.exe.


These scripts are provided as-is, free to copy and adapt. Use at your own risk.

Changing things

If something goes wrong

What you seeWhat to do
The window flashes and closesOpen a Command Prompt in the folder and run the .bat from there, so the text stays on screen.
“Windows protected your PC”Right-click the .zip → Properties → tick Unblock → OK, then unzip again.
Red text about parse errorsThe .ps1 was edited with curly quotes or special characters. Use straight quotes and save as plain text.
The sim opens flat on the monitorThe OpenXR runtime does not match the headset. Run Set_XR_Runtime.ps1 with no flags to see it, then -Meta, -VD or -Steam.
The sim sits at “loading” in VRThe runtime is SteamVR but SteamVR is not running. Start SteamVR first, or switch back with -Meta.
The overlay does not show over the simThe sim is in exclusive fullscreen. Use Windowed or Borderless.
[skip] RaceVisor not foundNot an error. RaceVisor is optional. To use it, set $RaceVisorExe at the top of Start_Sim.ps1 to the full path.

Per-sim settings, VR setup and capture troubleshooting are in the Controls Reference. Questions and fixes for other hardware are welcome on the Community page.

These scripts are provided as-is, free to copy and adapt. Use at your own risk.