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

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

Related

Can you dynamically set an attribute in a Powershell cmdlet call?

I am not sure if this is possible, but I am wondering if there's an elegant "dynamic" way to use or not an attribute when using a cmdlet in Powershell.
For instance, in the code below, how can i set the -directory attribute to be present or not, depending on some conditions?
gci $folder_root -recurse -directory | ForEach{
# do something
}
You can conditionally add parameter arguments to a call through a technique called splatting.
All you need to do is construct a dictionary-like object and add any parameters you might want to pass to the call there:
# Create empty hashtable to hold conditional arguments
$optionalArguments = #{}
# Conditionally add an argument
if($somethingThatMightBeTrue){
# This is equivalent to having the `-Directory` switch present
$optionalArguments['Directory'] = $true
}
# And invoke the command
Get-ChildItem $folder_root -Recurse #optionalArguments
Notice that any variable that we splat is specified with a # instead of $ at the call site.

Powershell pass command switches as variable

i'm trying to create a script that handle copy of folders and files.
i need to pass the switches '-recures -container' if it's a folder and nothing is it's a file.
is there a way to create a variable that will hold the '-recurse -container' and pass it to the command like this:
$copy_args = '-Recurse -container '
Copy-Item $tmptmp\$file -Destination \\$server\d$\$tmpprd\ $copy_args -Force
thanks
Mor
The best way to do this is with a technique called splatting. You create a hashtable of the parameters you want to pass and then you use # with the variable name (instead of $) to indicate that you want to splat it in to the required cmdlets parameters:
$copy_args = #{
Recurse = $true
Container = $true
}
Copy-Item $tmptmp\$file -Destination \\$server\d$\$tmpprd\ #copy_args -Force
Mark Wragg's helpful answer recommends splatting, which gives you the most flexibility.
As an aside:
Setting -Recurse is sufficient in your case, because it implies -Container
In fact, you can even use -Recurse unconditionally, because it is simply ignored if the source path is a file.
On occasion you may want to conditionally pass a switch directly, without the added verbosity of splatting.
Given that the syntax - -SomeSwitch:$boolVar or -SomeSwitch:(<boolExpression>) (optionally with whitespace after :) - isn't obvious, let me demonstrate:
Using a Boolean variable:
# The source path.
$sourcePath = $tmptmp\$file
# Set the Boolean value that will turn the -Recurse switch on / off.
$doRecurse = Test-Path -PathType Container $sourcePath # $true if $sourcePath is a dir.
# Use -Recurse:$doRecurse
Copy-Item -Recurse:$doRecurse $sourcePath -Destination \\$server\d$\$tmpprd\ -Force
Alternatively, using a Boolean expression:
Copy-Item -Recurse:(Test-Path -PathType Container $sourcePath) $sourcePath -Destination \\$server\d$\$tmpprd\ -Force
Note that the : to separate the parameter name from the argument is a necessity in the case of a switch parameter, so as to indicate that the argument is intended for the switch (which normally do not take an argument) rather than being a separate, positional argument.
Caveat: Both in this case and with splatting passing an effective $false to a switch is technically not the same as omitting the switch, and there are situations where the difference matters.
Read on to learn more.
Technically, a cmdlet or advanced function can distinguish between an omitted switch and one with a $false argument via the automatic $PSBoundParameters variable, which contains a dictionary of all explicitly passed parameters.
In the case of the common -Confirm parameter, this distinction is used intentionally - which is atypical.
Here's a simple demonstration:
# Sample advanced function that supports -Confirm with a medium impact level.
function foo {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')]
param()
if ($PSCmdlet.ShouldProcess('dummy')) { 'do it' }
}
# Invocation *with -Confirm* prompts unconditionally.
foo -Confirm # ditto with -Confirm:$true
# Invocation *without -Confirm*:
# Whether you'll be prompted depends on the value of the $ConfirmPreference
# variable: If the value is 'Medium' or 'Low', you'll be prompted.
foo
# Invocation with *-Confirm:$false* NEVER prompts,
# irrespective of the $ConfirmPreference value.
foo -Confirm:$false

Remove-Variable - Cannot find a variable with the name

I have a powershell script that with the following hashtable
[HashTable]$folder_and_prefix = #{}
After a certain point in my script I no longer need that hashtable, I tried doing:
Remove-Variable $folder_and_prefix
but I get the following error:
Remove-Variable : Cannot find a variable with the name
'System.Collections.Hashtable'
Is it possible to remove a hastable that I no longer need?
This is a common mistake. Remove-Variable is taking the name of the variable (a [String]), and by referencing the variable itself (with a dollar sign $) you are passing the value. Remove the dollar sign and that's all you need:
Remove-Variable folder_and_prefix
Further, it takes an array of names, so you can do:
$var1 = 5
$var2 = 'Hello'
$var3 = #{}
Remove-Variable var1,var2,var3
And it accepts wildcards:
Remove-Variable var*
The wildcard acceptance is also true for Set-Variable, Get-Variable, and Clear-Variable (New-Variable is the exception).
As mentioned by others, don't use the dollar sign ($) when referring to the variable itself. Additionally, I always check to see if variable exists (before attempting to delete it) using conditional statement as below:
if (Test-Path variable:myVar) {Remove-Variable myVar} #Delete only if local variable exists.
[int]$myVar = 5;
You can also check your variable's existence in a specific Scopes, such as, for Global scope:
if (Test-Path variable:global:myVar) {Remove-Variable myVar –Scope Global}
You can even check Environment variables as below:
if (Test-Path env:ComputerName) {"do something with $env:ComputerName."}
HTH

Use another cmdlet's parameter definition in 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

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