Santiago Squarzon's helpful answer contains some excellent sleuthing that reveals the hidden magic behind @args
, i.e. splatting using the automatic $args
variable, which is available in simple (non-advanced) functions only.
The solution in Santiago's answer isn't just complex, it also isn't fully robust, as it wouldn't be able to distinguish -ForegroundColor
(a parameter name) from '-ForegroundColor'
a parameter value that happens to look like a parameter name, but is distinguished from it by quoting.
- As an aside: even the built-in
@args
magic has a limitation: it doesn't correctly pass a [switch]
parameter specified with an explicit value through, such as
-NoNewLine:$false
[1]
A robust solution requires splatting via the automatic $PSBoundParameters
variable, which in turn requires that the wrapping function itself also declare all potential pass-through parameters.
Such a wrapping function is called a proxy function, and the PowerShell SDK facilitates scaffolding such functions via the PowerShell SDK, as explained in this answer.
In your case, you'd have to define your function as follows:
function Write-HostIfNotVerbose {
[CmdletBinding()]
param(
[Parameter(Position = 0, ValueFromPipeline, ValueFromRemainingArguments)]
[Alias('Msg', 'Message')]
$Object,
[switch] $NoNewline,
$Separator,
[System.ConsoleColor] $ForegroundColor,
[System.ConsoleColor] $BackgroundColor
)
begin {
$scriptCmd =
if ($VerbosePreference -eq 'SilentlyContinue') { { Write-Host @PSBoundParameters } }
else { { Out-Null } }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
process {
$steppablePipeline.Process($_)
}
end {
$steppablePipeline.End()
}
}
[1] Such an argument is invariably passed through as two arguments, namely as parameter name -NoNewLine
by itself, followed by a separate argument, $false
. The problem is that at the time the original arguments are parsed into $args
, it isn't yet known what formally declared parameters they will bind to. The NoteProperty
tagging applied to $args
for marking elements as parameter names doesn't preserve the information as to whether the subsequent argument was separated from the parameter name with :
, which for a [switch]
parameter is necessary to identify that argument as belonging to the switch. In the absence of this information, two separate arguments are always passed during splatting.
$args
as a parameter name, which you can't do, it's an automatic variable. – Artiodactyl$args
for a different parameter name, if you want to use splatting with an array instead of with a hash table (i.e.:Write-HostIfNotVerbose @{ForegroundColor='Green';Object='Hello'}
) you would need to do some obscure things, see his answer: https://mcmap.net/q/2035907/-in-powershell-how-to-pass-args-of-type-string-to-a-quot-function-param-quot-i-dont-want-to-pass-a-of-type-hashtable-to-the-function (at the end) – Artiodactyl$args
in a simple function is an array, not a hashtable and it works just fine there. – SternforemostInvoke-Expression
to get your expected results:Invoke-Expression "Write-Host $args"
. – Segovia"a b c"
is a string object a b c of length 5, it does not include quotes. If you want it to include quotes, you need to include them explicitly -'"a b c"'
. Have you tried your approach? – Sternforemost