Skip to content

Commit

Permalink
callRunner: Use PowerShell to run commands on Windows (#3006)
Browse files Browse the repository at this point in the history
* callRunner: Use PowerShell to run commands on Windows

PowerShell's `Start-Process` cmdlet & its `-Wait` parameter are used to wait
for a process tree to exit. This means we can now accurately track the "Playing"
 status of games on Windows.

* Properly quote runner path

* Simplify argument quoting a bit

There's no need to do different things depending on whether the argument
 contains a space, we can just always use the "longer" triple-quote form

* Only try to run PowerShell if it's available
  • Loading branch information
CommandMC authored Feb 6, 2024
1 parent 72be39a commit dd86ec7
Showing 1 changed file with 43 additions and 7 deletions.
50 changes: 43 additions & 7 deletions src/backend/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
flatPakHome,
isLinux,
isMac,
isWindows,
isSteamDeckGameMode,
runtimePath,
userHome
Expand Down Expand Up @@ -945,6 +946,8 @@ interface RunnerProps {

const commandsRunning = {}

let shouldUsePowerShell: boolean | null = null

function appNameFromCommandParts(commandParts: string[], runner: Runner) {
let appNameIndex = -1
let idx = -1
Expand Down Expand Up @@ -993,13 +996,34 @@ async function callRunner(
runner: RunnerProps,
options?: CallRunnerOptions
): Promise<ExecResult> {
const fullRunnerPath = join(runner.dir, runner.bin)
const appName = appNameFromCommandParts(commandParts, runner.name)

// Necessary to get rid of possible undefined or null entries, else
// TypeError is triggered
commandParts = commandParts.filter(Boolean)

let bin = runner.bin
let fullRunnerPath = join(runner.dir, bin)

// On Windows: Use PowerShell's `Start-Process` to wait for the process and
// its children to exit, provided PowerShell is available
if (shouldUsePowerShell === null)
shouldUsePowerShell =
isWindows && !!(await searchForExecutableOnPath('powershell'))

if (shouldUsePowerShell) {
const argsAsString = commandParts.map((part) => `"\`"${part}\`""`).join(',')
commandParts = [
'Start-Process',
`"\`"${fullRunnerPath}\`""`,
'-Wait',
'-ArgumentList',
argsAsString,
'-NoNewWindow'
]
bin = fullRunnerPath = 'powershell'
}

const safeCommand = getRunnerCallWithoutCredentials(
[...commandParts],
options?.env,
Expand Down Expand Up @@ -1032,8 +1056,6 @@ async function callRunner(
}
}

const bin = runner.bin

// check if the same command is currently running
// if so, return the same promise instead of running it again
const key = [runner.name, commandParts].join(' ')
Expand Down Expand Up @@ -1198,11 +1220,25 @@ function getRunnerCallWithoutCredentials(
const modifiedCommand = [...command]
// Redact sensitive arguments (Authorization Code for Legendary, token for GOGDL)
for (const sensitiveArg of ['--code', '--token']) {
const sensitiveArgIndex = modifiedCommand.indexOf(sensitiveArg)
if (sensitiveArgIndex === -1) {
continue
// PowerShell's argument formatting is quite different, instead of having
// arguments as members of `command`, they're all in one specific member
// (the one after "-ArgumentList")
if (runnerPath === 'powershell') {
const argumentListIndex = modifiedCommand.indexOf('-ArgumentList') + 1
if (!argumentListIndex) continue
modifiedCommand[argumentListIndex] = modifiedCommand[
argumentListIndex
].replace(
new RegExp(`"${sensitiveArg}","(.*?)"`),
`"${sensitiveArg}","<redacted>"`
)
} else {
const sensitiveArgIndex = modifiedCommand.indexOf(sensitiveArg)
if (sensitiveArgIndex === -1) {
continue
}
modifiedCommand[sensitiveArgIndex + 1] = '<redacted>'
}
modifiedCommand[sensitiveArgIndex + 1] = '<redacted>'
}

const formattedEnvVars: string[] = []
Expand Down

0 comments on commit dd86ec7

Please sign in to comment.