How to pass a mixture of strings and arrays to an executable from PowerShell?

I'm trying to call vswhere.exe to find various Visual Studio executables for CI purposes. In order to simplify this, I've created a wrapper function:

function Run-VsWhere { &("${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe") $args }

function Find-VSWherePath([string[]] $workloads, [string] $pathGlob) {
    Run-VsWhere -products * -prerelease -latest -requires $workloads -requiresAny -find $pathGlob
}

This works perfectly for single workloads, e.g. for MSBuild:

Find-VSWherePath "Microsoft.Component.MSBuild" "MSBuild/**/Bin/MSBuild.exe"
> C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\MSBuild\Current\Bin\MSBuild.exe

... but falls apart for multiple ones like VSTest:

Find-VSWherePath "Microsoft.VisualStudio.Workload.ManagedDesktop","Microsoft.VisualStudio.Workload.Web" "**/TestPlatform/vstest.console.exe"
> (nothing)

If I replace the call to vswhere with a call to echoargs, it demonstrates exactly what's going wrong. MSBuild:

> Arg 0 is <-products>
> Arg 1 is <*>
> Arg 2 is <-prerelease>
> Arg 3 is <-latest>
> Arg 4 is <-requires>
> Arg 5 is <Microsoft.Component.MSBuild>
> Arg 6 is <-requiresAny>
> Arg 7 is <-find>
> Arg 8 is <MSBuild/**/Bin/MSBuild.exe>

vs VSTest:

> Arg 0 is <-products>
> Arg 1 is <*>
> Arg 2 is <-prerelease>
> Arg 3 is <-latest>
> Arg 4 is <-requires>
> Arg 5 is <Microsoft.VisualStudio.Workload.ManagedDesktop Microsoft.VisualStudio.Workload.Web>
> Arg 6 is <-requiresAny>
> Arg 7 is <-find>
> Arg 8 is <**/TestPlatform/vstest.console.exe>

The issue is that the $workloads parameter is being passed to Run-VsWhere as a single parameter joined by a space, instead of one parameter per element in the array - how can I force it to pass as I need? I've tried every combination of splatting, splitting, joining, single-quoting, double-quoting... but nothing seems to work.

Upvotes: 0

Views: 262

Answers (2)

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200313

Using the automatic variable $args passes the arguments as they're provided, meaning that an array argument nested in $args is passed as-is (i.e. remains an array). Use splatting (@args) to flatten/unroll nested arrays.

function Run-VsWhere {
    & "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" @args
}

Upvotes: 1

Emiel Koning
Emiel Koning

Reputation: 4225

Is it possible your second call to Find-VSWherePath does not result in anything because the specified workloads are not available? I tried the code below and it does work.

function Find-VSWherePath([string[]] $workloads, [string] $pathGlob) {
    . "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" -products * -prerelease -latest -requires $workloads -requiresAny -find $pathGlob
}

clear

"This works"
Find-VSWherePath "Microsoft.Component.MSBuild" "MSBuild/**/Bin/MSBuild.exe"

"No result"
Find-VSWherePath "Microsoft.VisualStudio.Workload.Web" "MSBuild/**/Bin/MSBuild.exe"

"Try two workloads, the first is not available, but the second is. This also works."
Find-VSWherePath "Microsoft.VisualStudio.Workload.Web","Microsoft.VisualStudio.Component.NuGet" "MSBuild/**/Bin/MSBuild.exe"

Upvotes: 0

Related Questions