Backstory
I recently wrote a post about PowerShell Aliasing for the folks over at ScriptRunner.
The one issue I had while researching for this post was this:
You can not create a alias for a function and overwrite one of it’s parameters at the same time while keeping nice features like tab-complete.
For a concrete example of this visit please look at the original post.
At the end of the post I asked if anyone had a solution to this problem - and the internet delivered!
A Promising Idea
Within a few hours @AchimWieser got back to me with a solution.
Hi @ProfessorLogout ,
— Achim Wieser (@AchimWieser) December 2, 2020
I just read your blog about #PowerShell aliases. I think you can easily resolve the limitations concerning argument completion by using and advanced function as wrapper.
What do you think about this approach? https://t.co/xT1ibA9d3w pic.twitter.com/Qnla7UYit6
More or less exact replica of his code for readability:
function aliasLr {
[CmdletBinding()]
param (
[uint32]$Depth,
[Alias("ad","d")]
[switch]$Directory
)
process {
Get-ChildItem -Recurse @PSBoundParameters
}
}
New-Alias -Name "lr" -Value aliasLr -Force
# This will work now
lr -Depth 2 -d -Verbose
I want to point out that this was not the perfect solution for me, because it still lacks my two main wants for aliases:
- Simple & fast to create, which this is not as you would need to re-implement all parameters
- Allow to only set new “defaults” for some parameters and keep everything else as originally intended by the cmdlet (including tab-complete)
Nonetheless I want to thank Achim for providing his input and engaging with my post! Without him I would have probably never found the final solution adressing my two main wants.
Path To Success?
Rebuilding the original parameters, as Achim did, was not something that occurred to me before.
Given that new found angle on this I began diving into docs.microsoft.com and crazy technical blog posts from other creators.
First trying to build something based on runtime defined parameters1 and later the awesome New-DynamicParam from @PSCookieMonster aka Warren F.
Unfortunately this did not really click with me as those APIs are more C# than I can deal with late in the evening.
It Works
With this new found knowledge my searches drifted from something like “aliasing functions and overwrite parameters”
to “duplicate function with params”.
This is when I found PSScriptTools
and its function Copy-Command
2.
What it does is quite genius, it duplicates the function as PowerShell code, parameters and all,
and then just calls the original function with @PSBoundParameters
.
Given this it is easy to throw together a solution:
# Install PSScriptTools
Install-Module -Name PSScriptTools
# Copy our function to whatever we want the new name to be
Copy-Command -Command Get-ChildItem -NewName lr
In VSCode this even opens a new editor tab with the function pasted inside! From there we can change parameters to work as desired and save into the profile.
Thats it, job done! Not a beautiful solution but it works.
It Gets Even Better!
Granted, the above solution works. But it is cumbersome at best and straight up a bad idea at worst. Nonetheless it was an important step in finding what I believe to be the best solution to this (for now).
Lets take a look at what I cam up with:
-
First, we create our alias just like before, but directly for the cmdlet
New-Alias -Name "lr" -Value "Get-ChildItem" -Force
-
Second, we create a script block that evaluates by what name we were called and overwrites the returned value if we are called by our alias
$scriptBlock = { if ($MyInvocation.Line.StartsWith("lr")) { return $true } else { return $false } }
Credit where credit is due: I struggled with this part until I found this post by Tommy Maynard.
-
Finally, we create a default parameter value for the command we want to alias a parameter of with the script block
$PSDefaultParameterValues.Add("Get-ChildItem:Recurse",$scriptBlock)
Below is a quick example of lr with multiple parameters customized.
# Build lr just the way I want it
New-Alias -Name "lr" -Value "Get-ChildItem" -Force
$lrRecurse = {
if ($MyInvocation.Line.StartsWith("lr")) {
return $true
} else {
return $false
}
}
$PSDefaultParameterValues.Add("Get-ChildItem:Recurse", $lrRecurse)
$lrDepth = {
if ($MyInvocation.Line.StartsWith("lr")) {
return 2
}
}
$PSDefaultParameterValues.Add("Get-ChildItem:Depth", $lrDepth)
As you can see it is possible to add multiple parameter defaults per alias. There is actually nothing stopping us from defining multiple aliases for the same cmdlet and provide different defaults for each.
Info
It would absolutely be possible to create a helper function that brings the actual configuration down to one line. I would suggest something like this:
New-AdvancedAlias -Name "lr" -Value "Get-ChildItem" -OverwriteParameters @{"Recurse"=$true;"OtherParam"="Value"}
For now I do not have the time to implement this, but feel free to take a stab at it if you want! Just keep in mind that edge cases like multiple aliases changing the same parameter on the same cmdlet exist.
Maybe even switch to a dictionary based configuration of aliases all together to solve this?