Use another cmdlet's parameter definition in powershell? - powershell

In bash, I have a bunch of aliases that add parameters to existing programs/functions, for example:
alias grep='grep --color'
I know that's not the best analogy, but is there a simple way to do that in Powershell? It seems like Set-Alias doesn't let you specify parameters.
You can create an alias for a cmdlet, but you cannot create an alias
for a command that consists of a cmdlet and its parameters.
They suggest creating a new cmdlet to do so, but I'd prefer to be able to pass additional parameters without having to hardcode all the allowed params in the new cmdlet (like New-ProxyCommand seems to require you to do). That way, I don't have to know when the proxied/aliased cmdlet params change and change that in my proxy cmdlet.
So what's the best solution for
Not statically duplicating the parameter definition for the aliased/proxied cmdlet. Let the original cmdlet do the validation or dynamically refer to it.
Use an alias/differently named cmdlet so you have to do something explicit to get the different behavior
Have the alias/new cmdlet pass values to existing parameters in the aliased/proxied cmdlet
Closest I can think of is something like the below, though syntax is probably wrong. Also seems like it wouldn't play the nicest with piping, but that can probably be worked around somehow.
& $proxiedcommand $additionaldefaultparams $rawparamsfromread-host
Or is there a way to use the things for proxy cmdlets to dynamically instantiate parameters like below?
function aliased-cmdlet
{
[CmdletBinding((Get-Command Original-Cmdlet)._cmdletBindingsettings_)]
Param(
(Get-Command Original-Cmdlet)._paramsettings_)
)
Original-Cmdlet -CustomDefault Value -Whatever Else
}

If the only change you want to override is a default parameter value, there's already a built-in facility for that. Use the $PSDefaultParameterValues automatic variable:
PS C:\> ('a a a' |Select-String 'a').Matches.Count
1
PS C:\> $PSDefaultParameterValues['Select-String:AllMatches']=$true
PS C:\> ('a a a' |Select-String 'a').Matches.Count
3
If you want to override default parameter values in some instances, but not change the default behavior of the cmdlet, create a proxy command and set default values for the proxy command:
# Gather required info
$OriginalCommand = Get-Command Select-String
$NewCommandName = 'Select-AllMatches'
$Metadata = [System.Management.Automation.CommandMetadata]::new($OriginalCommand)
# Create proxy command
$ProxyString = [System.Management.Automation.ProxyCommand]::Create($Metadata)
New-Item -Path function:\ -Name $NewCommandName -Value $ProxyString
# Set default parameter values for proxy command
$PSDefaultParameterValues["$NewCommandName`:AllMatches"] = $true
Now the parameter default value is only overridden for Select-AllMatches:
PS C:\> ('a a a' |Select-String 'a').Matches.Count
1
PS C:\> ('a a a' |Select-AllMatches 'a').Matches.Count
3

Related

PowerShell custom output #{n=;e=}

I'm not very clear on how the #{n=;e=} construct works in PowerShell.
Does this type of thing have a name that I can find examples from?
For example, I find examples like this that works great:
gwmi win32_logicaldisk | Format-Table DeviceId, VolumeName, #{n="Size(GB)";e={[math]::Round($_.Size/1GB,2)}},#{n="Free(GB)";e={[math]::Round($_.FreeSpace/1GB,2)}}
When I try to do something like that, I can never get it to work. This works fine:
Get-Command -Module Microsoft.Powershell.Utility | Where CommandType -eq Function | Select Name,Version,CommandType
So I thought I would try and add the definition of that function to a new column using cat function:\$_.Name
Get-Command -Module Microsoft.Powershell.Utility | Where CommandType -eq Function | Select Name,Version,CommandType,#{n="Contents"; e={cat function:\$_.Name}}
But I just get an empty Contents column :(
Can someone give me some pointers on how the #{n=;e=} construct works?
Also, what do the n and e stand for?
#{n='';e={}} syntax is called a calculated property. n stands for Name and e stands for expression. You can even specify Name instead of n and Expression instead of e.
Calculated properties allow you to create new properties or change existing ones. This is done by passing a special hashtable to the Property parameter rather than a static property name. This is a useful feature where you create new properties using custom expression in a script block and use existing properties.
Not for only Select-Object this work for also Format-Table, Format-List cmdlets. These don't work outside this cmdlets.
Calculated properties are a quick way to manipulate command output to return just about anything you like. These save your time and reduce code length.
Sidenote: The last code in your question dosent work because you need to join two path using Join-Path. Calculated properties are innocent here. Even you can join path like this: cat "Function:\$($_.Name)" as #MathiasR.Jessen pointed.
#{} is a hashtable. {} is a scriptblock inside the hashtable set equal to e. The hashtable is used in a custom way for select-object.
$scriptblock = { $_ }
$hashtable = #{ name = 'number'
expression = $scriptblock
}
1..3 | select-object -property $hashtable
number
------
1
2
3

How to omit powershell parameter from cmdlet if value not provided?

I'm trying to execute a below Powershell command to create a new address list on exchange server with parameters like Name, Container, etc.
Container is an optional input/parameter, how do I omit it from cmdlet if its value is not provided?
I tried with IF conditionals but but does not seems working. Any help here?
New-AddressList -Name -Container \test MyAddressList5 -ConditionalStateOrProvince maha -IncludedRecipients MailboxUsers
You can pass needed parameters with their corresponding values via hashtable. Add If/Else conditions to include properties. Like so:
$Container = '\test MyAddressList5'
$Parameters = #{}
$Parameters.Add('ConditionalStateOrProvince','maha')
$Parameters.Add('IncludedRecipients','MailboxUsers')
if($Container){$Parameters.Add('Container',$Container)}
New-AddressList #Parameters
Also, when you need to include Switch parameter just pass $True. Like so:
$Parameters.Add('SomeSwitchParameter',$True)
Have a look at the documentation for the -Container parameter in New-AddressList: https://learn.microsoft.com/en-us/powershell/module/exchange/email-addresses-and-address-books/new-addresslist?view=exchange-ps#optional-parameters
Specifically:
If you don't use this parameter,the address list is created under the root (\).
...
Default value: None
$container = $null
New-AddressList -Container $container
# or...
$container = "\"
New-AddressList -Container $container
take a look at Get-Help *splatting to see a way to do this.
a bit more detail ... a "splat" is a hashtable of parameter = value pairs. once you have the basic always-there items in the splat, you can add others just as you would to any hashtable by $Param_Splat.Add(ParameterName, 'Value'). then when you call your cmdlet, you use Verb-Noun #Param_Splat. note the # symbol instead of the usual $. [grin]
take care,
lee

How to pipe objects to a specific parameter

I want to list all my PowerShell functions from one directory. The following command works:
Get-ChildItem -Path ($env:USERPROFILE + "\somewhere\*.psm1") -Recurse | ForEach-Object {Get-Command -Module $_.BaseName}
Now I tried to pipe the output from Get-ChildItem directly to the cmdlet Get-Command. Something like this, which does not work:
Get-ChildItem -Path ($env:USERPROFILE + "\somewhere\*.psm1") -Recurse | Get-Command -Module {$_.BaseName}
Obviously, I do not really understand how to pipe the object from Get-ChildItem in the correct way to the parameter -Module in Get-Command.
I have two questions:
Do you have a hint how to pipe correctly?
Is it possible to pipe to a specific parameter like -Module or is the object always handed over to one default parameter?
Parameters can be bound in four different ways:
By location in the argument list, e.g., Get-ChildItem C:\ (only certain parameters)
By name in the argument list, e.g. Get-ChildItem -Path C:\
By value from the pipeline, e.g. 1..5 | Get-Random (only certain parameters)
By name from the pipeline, e.g. 'C:\Windows' | Get-ChildItem (only certain parameters)
You can inspect the various ways of parameter binding via Get-Help <command> -Parameter *. You can then see that Get-Command allows the Module parameter to be bound only by property name:
-Module [<String[]>]
Specifies an array of modules. ...
Required? false
Position? named
Default value none
Accept pipeline input? True (ByPropertyName)
Accept wildcard characters? false
So the input has to be an object that has a Module property, to allow binding. In your case you thus need an additional step in between:
Get-ChildItem -Path ($env:USERPROFILE + "\somewhere\*.psm1") -Recurse |
Select-Object #{l='Module';e={$_.Basename}} |
Get-Command
Now, this instance here is something that's a bit annoying, since the Module parameter is bound by property name, but most things don't give you an object with a Module property. Heck, even Get-Module doesn't have that, since the returned object uses Name as the property name, so you can't even do
Get-Module | Get-Command
However, in many other places (notably concerning paths) work very well automatically. And if you can control your input objects, e.g. when reading from CSV or other data sources, you can end up with rather nice and concise code.
EDIT: Ansgar Wiechers notes that, while this should work, it doesn't, actually. This may be a shortfall of PowerShell's parameter binding algorithm (which is quite complex, as seen above, and we never got it to work correctly in Pash either), or maybe the Get-Command cmdlet has parameters described in a way that simply cannot allow binding because of reasons.

common ways to pass state from cmdlet to cmdlet

I am creating my own set of cmdlets. They all need the same state data (like location of DB and credentials for connecting to DB). I assume this must be a common need and wonder what the common idiom for doing this is.
the obvious one is something like
$db = my-make-dbcreds db=xxx creds=yyyy ...
my-verb1 $db | my-verb2 $db -foo 42...
my-verb8 $db bar wiz
.....
but i was wondering about other ways. Can I silently pipe the state from one to another. I know I can do this if state is the only thing I pipe but these cmdlets return data
Can I set up global variables that I use if the user doesnt specify state in the command
Passing the information state through the pipe is a little lost on me. You could update your cmdlets to return objects that the next cmdlet will accept via ValueFromPipeline. When you mentioned
like location of DB and credentials for connecting to DB
the best this I could think that you want is....
SPLATTING!
Splatting is a method of passing a collection of parameter
values to a command as unit. Windows PowerShell associates
each value in the collection with a command parameter.
In its simplest form
$params = #{
Filter = "*.txt"
Path = "C:\temp"
}
Get-ChildItem #params
Create a hashtable of parameters and values and splat them to the command. The you can edit the table as the unique call to the cmdlet would allow.
$params.Path = "C:\eventemperor"
Get-ChildItem #params
I changed the path but left the filter the same. You also dont have to have everything in $params you splat and use other parameters in the same call.
It is just a matter of populating the variables as you see fit and changing them as the case requires.
Spewing on the pipeline
Pretty that is what it is actually called. If you use advanced function parameters you can chain properties from one cmdlet to the next if you really wanted to. FWIW I think splatting is better in your case but have a look at the following.
function One{
param(
[parameter(Mandatory=$true,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$true)]
[String[]]
$Pastry
)
write-host "You Must Like $Pastry"
Write-Output (New-Object -TypeName PSCustomObject -Property #{Pastry= $pastry})
# If you have at least PowerShell 3.0
# [pscustomobject]#{Pastry= $pastry}
}
Simple function that writes the variable $pastry to the console but also outputs an object for the next pipe. So running the command
"Eclairs" | One | One | Out-Null
We get the following output
You Must Like Eclairs
You Must Like Eclairs
We need to pipe to Out-Null at the end else you would get this.
Pastry
------
{Eclairs}
Perhaps not the best example but you should get the idea. If you wanted to extract information between the pipe calls you could use Tee-Object.
"Eclair" | One | Tee-Object -Variable oneresults | One | Out-Null
$oneresults
Consider Parameter Default Values
Revisiting this concept after trying to find a better way to pass SQL connection information between many function working against the same database. I am not sure if this is the best thing to do but it certainly simplifies thing for me.
The basic idea is to add a rule for your cmdlet or wildcard rule if your cmdlets share a naming convention. For instance I have a series of functions that interact with our ticketing system. They all start with Get-Task.... and all configured with SQL connection information.
$invokeSQLParameters = #{
ServerInstance = "serverName"
Username = $Credentials.UserName
Password = $Credentials.GetNetworkCredential().Password
}
$PSDefaultParameterValues.Add("New-Task*:Connection",$invokeSQLParameters)
$PSDefaultParameterValues.Add("Get-Task*:Connection",$invokeSQLParameters)
So now in my functions I have a parameter called Connection that will always be populated with $invokeSQLParameters as long as the above is done before the call. I still use splatting as well
Invoke-Sqlcmd -Query $getCommentsQuery #Connection
You can read up more about this at about_parameters_default_values

Powershell: Colon in commandlet parameters

What's the deal with Powershell commandlet switch parameters that require a colon?
Consider Exchange 2010 management shell cmdlet Move-ActiveMailboxDatabase. The Confirm switch is a System.Management.Automation.SwitchParameter and must be used like so,
Move-ActiveMailboxDatabase -Confirm:$false
Without the colon the command fails to recognize the don't confirm switch like so,
Move-ActiveMailboxDatabase -Confirm $false
Why is that? What's the difference the colon makes there? Why Exchange2010 seems to be about the only thing I've noticed this behavior?
I've browsed through Powershell in Action and Powershell 2.0, but didn't find anything about this syntax. Scope resolution and .Net object access uses are documented on those books though.
My Google-fu found an article which claims that it explicitly forwards switch parameter values, but fails to explain what that is about.
When you do:
Move-ActiveMailboxDatabase -Confirm $false
you are not saying Confirm parameter accepts the $false. You are saying -Confirm and also passing an (separate) argument to the cmdlet with value $false.
Since Confirm is a switch, just the presence of -Confirm means it is true. Absence of -Confirm means it is false.
Let me give you a script example:
param([switch]$test)
write-host Test is $test
If you just run the script without any arguments / paramters i.e .\script.ps1 you get output:
Test is False
If you run it as .\script.ps1 -test, the output is
Test is True
If you run it as .\script.ps1 -test $false, the output is
Test is True
If you run it as .\script.ps1 -test:$false the output is
Test is False
It is in scenarios where the value for a switch variable itself has to be determined from another variable that the : is used.
For example, consider the script:
param ([boolean]$in)
function func([switch] $test){
write-host Test is $test
}
func -test:$in
Here if you run it as .\script.ps1 -in $false, you get
Test is false
If you weren't able to use the :, you would have had to write it as:
if($in){ func -test}
else { func }
The colon can be used with every parameter value but is more special in the case of switch parameters. Switch parameters don't take values, they are either present ($true) or absent ($false).
Imagine you have a function like this:
function test-switch ([string]$name,[switch]$force) { ... }
And you call it like so:
test-switch -force $false
Since switch parameters are either present or not, $false would actually bind to the Name parameter. So, how do you bind a value to a switch parameter? With the colon:
test-switch -force:$false
Now the parameter binder knows which parameter the value goes to.