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
- Before unzipping, right-click the .zip → Properties → tick Unblock → OK. This stops Windows warning about each script.
- Unzip the folder anywhere, for example
Documents\RaceVisor-Launchers. Keep all the files together. - Optional: if you use RaceVisor, put
RaceVisor.exein the same folder or set its path in the next step. The launchers work without it — it is simply skipped. - Open
Start_Sim.ps1in Notepad and look at the block marked CONFIGURE HERE. Remove the apps you do not use and add your own wheel software. - Double-click the
.batfor your sim. - Read what it prints. Each line starts with a tag:
| Tag | Meaning |
|---|---|
[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:
| Step | What | Why it goes here |
|---|---|---|
| 1 | OpenXR runtime, and SteamVR if the runtime points at it | With SteamVR selected but not running, the sim sits at “loading” forever with no error. |
| 2 | Wheel software | Started after the sim, the wheel comes up with default rotation and force feedback. |
| 3 | OpenKneeboard | The sim only picks up the kneeboard layer if it is already there. |
| 4 | RaceVisor | Started after a fullscreen sim, the overlay can knock the sim out of its display mode. |
| 5 | The sim | Steam games go through Steam, so Steam starts itself if it is closed. |
| 6 | OBS, last | Started 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.
For this setup
Download both and keep them in the same folder. Double-click the .bat to run; it calls the .ps1.
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.
| File | What it is |
|---|---|
Start_iRacing.bat | Double-click launcher for iRacing. |
Start_AC.bat | Double-click launcher for Assetto Corsa. |
Start_ACC.bat | Double-click launcher for Assetto Corsa Competizione. |
Start_AMS2.bat | Double-click launcher for Automobilista 2. |
Start_RF2.bat | Double-click launcher for rFactor 2. |
Start_Sim.ps1 | The script that does the work. The .bat files call it. |
Set_XR_Runtime.ps1 | Shows or changes the active OpenXR runtime. |
Stop_SteamVR.bat / .ps1 | Stops SteamVR and goes back to the Meta runtime. |
README.txt | The 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.
@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.
| Flag | What it does |
|---|---|
-Sim <name> | Required. One of iRacing, AC, ACC, AMS2, RF2. The .bat files fill this in for you. |
-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 and runtime side, but leave OBS alone. |
-Force | Start 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:
| Setting | What it is |
|---|---|
$StartRaceVisor | $true or $false. RaceVisor is optional; the launcher works without it. |
$RaceVisorExe | Full path to RaceVisor.exe. Leave as '' to look next to the script and in the usual folders. |
$ObsExe | Full path to obs64.exe. Leave as '' to find it automatically. |
$PreSimApps | The 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. |
$iRacingUi | Path to the iRacing launcher. Only change it if iRacing is installed somewhere unusual. |
# 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.
| Flag | What it does |
|---|---|
| (none) | Shows which runtime is active. Changes nothing. |
-Meta | Meta Quest over the Link cable or Air Link. |
-VD | Virtual Desktop (wireless). |
-Steam | SteamVR. |
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.
# 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.
| Flag | What it does |
|---|---|
| (none) | Stops SteamVR and sets the OpenXR runtime back to Meta. |
-CloseSteam | Also closes the Steam client. |
-KeepRuntime | Stops SteamVR but leaves the OpenXR runtime alone. Use this if SteamVR is your normal runtime (Index, Vive, Pimax and so on). |
@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 - 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.
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
- Different wheel. Edit
$PreSimAppsinStart_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 = @{ ... }, give it a new name, set its Steam app id (or its path) and the process that runs while you are driving. Add the name to theValidateSetlist near the top, then copy a .bat and change-Sim. Or use the generator with “Another sim”. - Finding a process name. Start the program, open Task Manager → Details, and use the name in the first column without
.exe. - Editing safely. Use Notepad and straight quotes only. One curly quote pasted from a web page or a document produces a screen of red errors.
If something goes wrong
| What you see | What to do |
|---|---|
| The window flashes and closes | Open 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 errors | The .ps1 was edited with curly quotes or special characters. Use straight quotes and save as plain text. |
| The sim opens flat on the monitor | The 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 VR | The runtime is SteamVR but SteamVR is not running. Start SteamVR first, or switch back with -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. |
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.