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

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.

Related

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

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

PowerShell Test-Path -exclude

I need to perform a check on a target folder, and check if the file is from today and has more than 5kb
The below command provide a bool value based on the existence of the file using the today date, but I would like to add also an exclusion like -gt5kb
I tried to use -Exlcude but I'm not sure on how it work.
$integration = Test-Path 'C:\Users\EA\Desktop\CATS HTML*' -NewerThan (Get-Date -UFormat "%d/%m/%Y")
Do you have any advice on how can I include also the size check in the same statement?
This is not possible with the Test-Path cmdlet.
The exclude parameter is defined as follow:
Specifies items that this cmdlet omits. The value of this parameter
qualifies the Path parameter. Enter a path element or pattern, such as
"*.txt". Wildcard characters are permitted.
You will need a second method to perform the check. Here an example using the Get-Item cmdlet:
if (Get-Item 'yourfile.html'| Where-Object Length -gt 5kb) {
# do something
}

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.

PowerShell: How can I pipeline objects to a parameter that accepts input "ByPropertyName"?

Here is a sample PowerShell script (it doesn't work) that illustrates what I want to do:
$BuildRoot = '_Provided from script parameter_'
$Files = 'a.dll', 'b.dll', 'c.dll'
$BuiltFiles = $Files | Join-Path $BuildRoot
I have a list of filenames, and a directory name, and I want to join them all together, simple. The problem is that this doesn't work because the Join-Path parameter -ChildPath accepts input from the pipeline ByPropertyName, so the following error is reported:
The input object cannot be bound to any parameters for the command
either because the command does not take pipeline input or the input
and its properties do not match any of the parameters that take
pipeline input.
I can "fix" it by changing the line to the following:
$BuiltFiles = $Files | Select #{ Name = "ChildPath"; Expression = {$_}} | join-path $BuildRoot
Basically, the select operation is turning the object into a property value. This works, but it introduces a lot of syntactic noise to accomplish something that seems so trivial. If this is the only way to do it, so be it, but I'd like to make this script maintainable for people in the future, and this is a little hard to grok at first glance.
Is there a cleaner way to accomplish what I'm trying to do here?
You can accomplish this a little easier like so:
$Files = 'a.dll', 'b.dll', 'c.dll'
$Files | Join-Path $BuildRoot -ChildPath {$_}
Note: you don't want to put {} around the files. That creates a scriptblock in PowerShell which is essentially an anonymous function. Also, when a parameter is pipeline bound you can use the trick of supplying a scriptblock ({}) where $_ is defined in that scriptblock to be the current pipeline object.