Get-ChildItem recurse as a parameter in PowerShell - powershell

I am looking to create a function that could toggle the ability to recurse in cmdlet Get-ChildItem.
As a very basic example:
...
param
(
[string] $sourceDirectory = ".",
[string] $fileTypeFilter = "*.log",
[boolean] $recurse = $true
)
Get-ChildItem $sourceDirectory -recurse -filter $fileTypeFilter |
...
How does one conditionally add the -recurse flag to Get-ChildItem without having to resort to some if/else statement?
I thought perhaps one could just substitute the -recurse in the Get-ChildItem statement with a $recurseText parameter (set to "-recurse" if $recurse were true), but that does not seem to work.

A couple of things here. First, you don't want to use [boolean] for the type of the recurse parameter. That requires that you pass an argument for the Recurse parameter on your script e.g. -Recurse $true. What you want is a [switch] parameter as shown below. Also, when you forward the switch value to the -Recurse parameter on Get-ChildItem use a : as shown below:
param (
[string] $sourceDirectory = ".",
[string] $fileTypeFilter = "*.log",
[switch] $recurse
)
get-childitem $sourceDirectory -recurse:$recurse -filter $fileTypeFilter | ...

The PowerShell V1 way to approach this is to use the method described in the other answers (-recurse:$recurse), but in V2 there is a new mechanism called splatting that can make it easier to pass the arguments from one function to another.
Splatting will allow you to pass a dictionary or list of arguments to a PowerShell function. Here's a quick example.
$Parameters = #{
Path=$home
Recurse=$true
}
Get-ChildItem #Parameters
Inside of each function or script you can use $psBoundParameters to get the currently bound parameters. By adding or removing items to $psBoundParameters, it's easy to take your current function and call a cmdlet with some the functions' arguments.
I hope this helps.

I asked a similar question before... My accepted answer was basically that in v1 of PowerShell, just passing the named parameter through like:
get-childitem $sourceDirectory -recurse:$recurse -filter ...

Here's a good list of the types of parameters you can use:
param(
[string] $optionalparam1, #an optional parameter with no default value
[string] $optionalparam2 = "default", #an optional parameter with a default value
[string] $requiredparam = $(throw ""requiredparam required."), #throw exception if no value provided
[string] $user = $(Read-Host -prompt "User"), #prompt user for value if none provided
[switch] $switchparam; #an optional "switch parameter" (ie, a flag)
)
From here

Related

Powershell command Start-BitsTransfer fails when source file paths contain square brackets [] [duplicate]

Is there a function in PowerShell for escaping characters in paths?
NB: I'm aware that most cmdlets which provide a Path parameter also provide a LiteralPath parameter which resolves this issue. This question is more from curiosity than need, as in most use cases I can think of, switching to LiteralPath makes sense. However there are some genuine use-cases (e.g. Start-BitsTransfer has a Source parameter, but no literal equivalent).
Detail
If I have a file c:\temp\test[0123].txt, instead of Get-Item 'c:\temp\test[0123].txt', I'd have to use Get-Item 'c:\temp\test`[0123`].txt' to get a result (or make use of the LiteralPath parameter).
Even the path returned by another PowerShell command returns an unescaped string; i.e. Get-ChildItem 'c:\temp\' -Filter 'test*.txt' | Convert-Path | Get-Item fails (NB: if we pass the actual FileSystemInfo object all works, but that object has no properties with the correctly escaped string path).
We can easily escape this using code such as below:
$path = 'c:\temp\test[0123].txt'
$path = $path -replace '([][[])', '`$1' # escape square brackets by adding back ticks
Get-Item $path
However when escaping strings, standard advice is to avoid rolling your own solution / to make use of the language's solutions to these issues.
Is there any pre-existing function in PowerShell for this purpose, or any recommended way of approaching this; or is the only option to use a bespoke function (e.g. below)?
function ConvertFrom-LiteralPath {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$LiteralPath
)
process {
(New-Object -TypeName 'PSObject' -Property #{
LiteralPath = $LiteralPath
Path = $LiteralPath -replace '([][[\*\?])', '`$1' # escapes ], [, *, and ?
})
}
}
Info on special characters:
Wildcard Queries: https://technet.microsoft.com/en-us/library/ee692793.aspx
File Paths: https://technet.microsoft.com/en-us/library/ff730956.aspx
There is the following method you can use:
[Management.Automation.WildcardPattern]::Escape('test[1].txt')
Returns:
test`[1`].txt
Documented here:
https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.wildcardpattern.escape

How to add -confirm:$false in a powershell hashtable?

$cmdlet="Disable-RemoteMailBox"
$arguments = #{Identity=$identity;DomainController=$domaincontroller;Archive=""}
$command_args=""
$arguments.keys | ForEach-Object{
$message = '-{0} {1} ' -f $_, $arguments[$_]
$command_args+= $message
}
$result=& $cmdlet #arguments 2>&1
In the end this is executed:
Disable-RemoteMailBox -Identity abc#corp.com -DomainController dc.corp.local -Archive
but i need to add a confirm:$false
Disable-RemoteMailBox -Identity abc#corp.com -DomainController dc.corp.local -Archive -Confirm:$false
How to add this $false in the Hashtable?
Change the $arguments hashtable from:
$arguments = #{Identity=$identity;DomainController=$domaincontroller;Archive=""}
to
$arguments = #{Identity=$identity;DomainController=$domaincontroller;Archive="";Confirm=$false}
Adding to Mathias's concise answer
Excepting for the confirm functionality's integration with the $ConfirmPreference preference variable, the -Confirm common parameter can be looked at as a simple switch parameter. It is either present or not present. However, PowerShell's internal type conversion engine will evaluate a [Switch] more like a [Boolean] You can see this if you cast a [Bool] to a [Switch].
[Switch]$true or [Switch]$false will return IsPresent True/False respectively.
If you specify Confirm = $false in a splatting hash table, the type coercion (casting) that occurs during the parameter binding will handle it correctly. This is also true for any other switch parameter, even custom ones you define in your custom functions. This type conversion is also noticiable when you need to evaluate a switch parameter internal to a function.
If I specify a switch parameter named $Delete
Param( [Switch]$Delete )
Then internally I can execute logic like:
If( $Delete -eq $true ) {
# Delete the file or whatever...
}
Of course, you can shorten to:
If( $Delete ) {
# Delete the file or whatever...
}
However, you don't need a deep understanding of PowerShell's type conversion system to use Boolean or Switch parameters in splatting hash tables. It's documented in about_Splatting. The first few lines will explain hash table splatting of switch parameters.

Mutually exclusive switch parameters

The function has one required parameter, -Path, and two other mutually exclusive switches. This is not the real function, but a MRE (Minimal Reproducable Example). The default operation is to copy the file to a known location and then remove it.
Do-TheFile [-Path] <String[]> [[-Copy] | [-Remove]]
-Path = filename is mandatory
-CopyOnly = only copy the file, cannot be used with -Remove
-RemoveOnly = only remove the file, cannot be used with -Copy
This is the current code.
param (
[Parameter(Mandatory=$true, Position=0)]
[string[]]$Path
,[Parameter(Mandatory=$false, ParameterSetName='CopyOnly')]
[switch]$CopyOnly
,[Parameter(Mandatory=$false ,ParameterSetName='RemoveOnly')]
[switch]$RemoveOnly
)
The console allows me to specify both -CopyOnly and -RemoveOnly. My expectation was that the console would not permit me to enter both -CopyOnly and -RemoveOnly because they are in different ParameterSets. How can I specify these ParameterSets so that -Copy and -Remove are mutually exclusive?
PS C:\src\t> Do-TheFile -Path t.txt -CopyOnly -RemoveOnly
Do-TheFile: Parameter set cannot be resolved using the specified named parameters. One or more parameters issued cannot be used together or an insufficient number of parameters were provided.
Agree with the others here.
Your code works, as written when using IntelliSense, but PowerShell will not stop you from typing in other valid switches/variables/property names (in either the consolehost, ISE, VSCode, Visual Studio, etc...), that does not mean it would work just because you typed both.
Why make two switches, when you only want to use one option at a time, no matter what.
Just use a simple validation set.
Function Test-MyFunctionTest
{
[cmdletbinding()]
param
(
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Path,
[Parameter(Mandatory)][ValidateSet('CopyOnly', 'RemoveOnly')]
[string]$FileAction
)
}
# Results
<#
Test-MyFunctionTest -Path $PWD -FileAction CopyOnly
Test-MyFunctionTest -Path $PWD -FileAction RemoveOnly
#>
Otherwise, as you have discovered, you have to code this up yourself. For example:
Function Test-MyFunctionTestAgain
{
[cmdletbinding()]
param
(
[Parameter(Mandatory=$true, Position=0)]
[string[]]$Path,
[switch]$RemoveOnly
)
If($RemoveOnly.IsPresent)
{'Do the remove action'}
Else {'Do the copy action'}
}
Test-MyFunctionTestAgain -Path $PWD
# Results
<#
Do the copy action
#>
Test-MyFunctionTestAgain -Path $PWD -RemoveOnly
# Results
<#
Do the remove action
#>
Update
As for this...
"I agree that this could work. Although, the default operation (using
no switches) is to both Copy and Remove."
... then this...
Function Test-MyFunctionTestMore
{
[cmdletbinding()]
param
(
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Path,
[Parameter(Mandatory = $false)][ValidateSet('CopyAndRemove', 'CopyOnly', 'RemoveOnly')]
[string]$FileAction = 'CopyAndRemove'
)
Switch ($FileAction)
{
CopyAndRemove {'Do the copy and remove action'}
CopyOnly {'Do the copy only action'}
RemoveOnly {'Do the remove only action'}
}
}
Test-MyFunctionTestMore -Path $PWD
# Results
<#
Do the copy and remove action
#>
Test-MyFunctionTestMore -Path $PWD -FileAction CopyOnly
# Results
<#
Do the copy only action
#>
Test-MyFunctionTestMore -Path $PWD -FileAction RemoveOnly
# Results
<#
Do the remove only action
#>
Or this way, if you are really yearning just to have a switch ;-} ...
Function Test-MyFunctionTestSwitch
{
[cmdletbinding()]
param
(
[Parameter(Mandatory=$true, Position=0)]
[string[]]$Path,
[Parameter(Mandatory = $false)][ValidateSet('CopyAndRemove', 'CopyOnly', 'RemoveOnly')]
[string]$FileAction = 'CopyAndRemove',
[switch]$RemoveOnly
)
If($RemoveOnly.IsPresent)
{
$FileAction = 'RemoveOnly'
'Do the remove only action'
}
ElseIf ($FileAction -eq 'CopyOnly')
{'Do the copy only action'}
Else{'Do the copy and remove action'}
}
Test-MyFunctionTestSwitch -Path $PWD
# Results
<#
Do the copy and remove action
#>
Test-MyFunctionTestSwitch -Path $PWD -FileAction CopyOnly
# Results
<#
Do the copy only action
#>
Test-MyFunctionTestSwitch -Path $PWD -RemoveOnly
# Results
<#
Do the remove only action
#>
Lastly as a point of note:
Trying to emulate some other tools actions, or expecting PowerShell to natively emulate some other tools actions, params, etc., really should not be an expectation.
If you believe PowerShell should have a specific feature, then the option is to submit it to the PowerShell team, to have it upvoted by others for work/inclusion or since PowerShell is open-sourced, you can tool it up and submit it for review/approval of commit.
In contemporary versions of PowerShell, ParameterSets are mutually exclusive.
function greet {
Param(
[String]
$Name = "World",
[Parameter(ParameterSetName="intro")]
[Switch]
$Hello,
[Parameter(ParameterSetName="outro")]
[Switch]
$Farewell
)
if ($Hello) {
echo "Hello, $Name!"
} elseif ($Farewell) {
echo "Farewell, $Name!"
} else {
echo "What's up, $Name"
}
}
This results in the split-groupings that you often see in MS cmdlets:
> greet -?
NAME
greet
SYNTAX
greet [-Name <string>] [-Hello] [<CommonParameters>]
greet [-Name <string>] [-Farewell] [<CommonParameters>]
Doing this requires that the user or the script identify which ParameterSet should be used.
greet -Name "Bob"
> greet -Name "Bob"
greet: Parameter set cannot be resolved using the specified named parameters. One or more parameters issued cannot be used together or an insufficient number of parameters were provided.
This is trying to tell the user they weren't specific enough. See DefaultParameterSetName for how to set it from the script:
There is a limit of 32 parameter sets. When multiple parameter sets are defined, the DefaultParameterSetName keyword of the CmdletBinding attribute specifies the default parameter set. PowerShell uses the default parameter set when it can't determine the parameter set to use based on the information provided to the command.
#Postanote's answer is great and I will prefer it.
However, as #kfsone underlined, DefaultParameterSetName can achieve this with your two switches if you add a ParameterSetName for $Path only and set it as default :
[CmdletBinding(DefaultParameterSetName='CopyAndRemove')]
param (
[Parameter(Mandatory=$true, Position=0)]
[Parameter(ParameterSetName='CopyAndRemove')]
[Parameter(ParameterSetName='CopyOnly')]
[Parameter(ParameterSetName='RemoveOnly')]
[string[]]$Path,
[Parameter(Mandatory=$false, ParameterSetName='CopyOnly')]
[switch]$CopyOnly,
[Parameter(Mandatory=$false ,ParameterSetName='RemoveOnly')]
[switch]$RemoveOnly
)
$Path
$PSCmdlet.ParameterSetName

How to check command-line parameter from a PowerShell module?

Is there a way to check if a command-line parameter was specified for a PowerShell script from a module (.psm1 file)? I do not need the value, just have to know if the parameter was specified. The $PSBoundParameters.ContainsKey method does not seem to work.
TestParam.psm1:
function Test-ScriptParameter {
[CmdletBinding()]
param ()
# This does not work (always returns false):
return $PSBoundParameters.ContainsKey('MyParam')
}
Export-ModuleMember -Function *
TestParam.ps1:
[CmdletBinding()]
param (
$MyParam= "Default"
)
$path = Join-Path (Split-Path -Path $PSCommandPath -Parent) 'TestParam.psm1'
Import-Module $path -ErrorAction Stop -Force
Test-ScriptParameter
This must return false:
PS>.\TestParam.ps1
This must return true:
PS>.\TestParam.psq -MyParam ""
This must return true:
PS>.\TestParam.ps1 -MyParam "Runtime"
It cannot be done like you are thinking about it. The PSBoundParameters variable is native to the cmdlet's execution and as such depends on the param block of the cmdlet's definition. So in your case, the Test-ScriptParameter is checking if it was invoked with the parameter MyParam but since it doesn't specify it, then it will be always false.
To achieve what I believe you want, you need to create a function that checks into a hash structure like the PSBoundParameters for a specific key. The key needs to be provided by name. But then a simple $PSBoundParameters.ContainsKey('MyParam') wherever you need it should suffice.
The problem with your code is that you are checking the $PSBoundParameters value of the Function itself, which has no parameters.
You could make the function work by sending the $PSBoundParameters variable from the script in to the function via a differently named parameter.
For example:
TestParam.psm1:
function Test-ScriptParameter ($BoundParameters) {
return $BoundParameters.ContainsKey('MyParam')
}
Export-ModuleMember -Function *
TestParam.ps1:
[CmdletBinding()]
param (
$MyParam = "Default"
)
$path = Join-Path (Split-Path -Path $PSCommandPath -Parent) 'TestParam.psm1'
Import-Module $path -ErrorAction Stop -Force
Test-ScriptParameter $PSBoundParameters

How do you support PowerShell's -WhatIf & -Confirm parameters in a Cmdlet that calls other Cmdlets?

I have a PowerShell script cmdlet that supports the -WhatIf & -Confirm parameters.
It does this by calling the $PSCmdlet.ShouldProcess() method before performing the change.
This works as expected.
The problem I have is that my Cmdlet is implemented by calling other Cmdlets and the -WhatIf or -Confirm parameters are not passed along to the Cmdlets I invoke.
How can I pass along the values of -WhatIf and -Confirm to the Cmdlets I call from my Cmdlet?
For example, if my Cmdlet is Stop-CompanyXyzServices and it uses Stop-Service to implement its action.
If -WhatIf is passed to Stop-CompanyXyzServices I want it to also be passed to Stop-Service.
Is this possible?
Passing parameters explicitly
You can pass the -WhatIf and -Confirm parameters with the $WhatIfPreference and $ConfirmPreference variables. The following example achieves this with parameter splatting:
if($ConfirmPreference -eq 'Low') {$conf = #{Confirm = $true}}
StopService MyService -WhatIf:([bool]$WhatIfPreference.IsPresent) #conf
$WhatIfPreference.IsPresent will be True if the -WhatIf switch is used on the containing function. Using the -Confirm switch on the containing function temporarily sets $ConfirmPreference to low.
Passing parameters implicitly
Since the -Confirm and -WhatIf temporarily set the $ConfirmPreference and $WhatIfPreference variables automatically, is it even necessary to pass them?
Consider the example:
function ShouldTestCallee {
[cmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]
param($test)
$PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Confirm?")
}
function ShouldTestCaller {
[cmdletBinding(SupportsShouldProcess=$true)]
param($test)
ShouldTestCallee
}
$ConfirmPreference = 'High'
ShouldTestCaller
ShouldTestCaller -Confirm
ShouldTestCaller results in True from ShouldProcess()
ShouldTestCaller -Confirm results in an confirm prompt even though I didn't pass the switch.
Edit
#manojlds answer made me realize that my solution was always setting $ConfirmPreference to 'Low' or 'High'. I have updated my code to only set the -Confirm switch if the confirm preference is 'Low'.
After some googling I came up with a good solution for passing common parameters along to called commands. You can use the # splatting operator to pass along all the parameters that were passed to your command. For example, if
Start-Service -Name ServiceAbc #PSBoundParameters
is in the body of your script powershell will pass all the parameters that were passed to your script to the Start-Service command. The only problem is that if your script contains say a -Name parameter it will be passed too and PowerShell will complain that you included the -Name parameter twice. I wrote the following function to copy all the common parameters to a new dictionary and then I splat that.
function Select-BoundCommonParameters
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
$BoundParameters
)
begin
{
$boundCommonParameters = New-Object -TypeName 'System.Collections.Generic.Dictionary[string, [Object]]'
}
process
{
$BoundParameters.GetEnumerator() |
Where-Object { $_.Key -match 'Debug|ErrorAction|ErrorVariable|WarningAction|WarningVariable|Verbose' } |
ForEach-Object { $boundCommonParameters.Add($_.Key, $_.Value) }
$boundCommonParameters
}
}
The end result is you pass parameters like -Verbose along to the commands called in your script and they honor the callers intention.
Here is a complete solution based on #Rynant and #Shay Levy's answers:
function Stop-CompanyXyzServices
{
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]
Param(
[Parameter(
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true
)]
[string]$Name
)
process
{
if($PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Stop XYZ services '$Name'")){
ActualCmdletProcess
}
if([bool]$WhatIfPreference.IsPresent){
ActualCmdletProcess
}
}
}
function ActualCmdletProcess{
# add here the actual logic of your cmdlet, and any call to other cmdlets
Stop-Service $name -WhatIf:([bool]$WhatIfPreference.IsPresent) -Confirm:("Low","Medium" -contains $ConfirmPreference)
}
We have to see if -WhatIf is passed separately as well so that the whatif can be passed on to the individual cmdlets. ActualCmdletProcess is basically a refactoring so that you don't call the same set of commands again just for the WhatIf. Hope this helps someone.
Updated per #manojlds comment
Cast $WhatIf and $Confirm to Boolean and pass the values to the the underlying cmdlet:
function Stop-CompanyXyzServices
{
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
Param(
[Parameter(
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true
)]
[string]$Name
)
process
{
if($PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Stop service '$Name'"))
{
Stop-Service $name -WhatIf:([bool]$WhatIf) -Confirm:([bool]$confirm)
}
}
}
Just so you wont get run around the block for hours by this question and the answers here, I would suggest that you read this article instead:
https://powershellexplained.com/2020-03-15-Powershell-shouldprocess-whatif-confirm-shouldcontinue-everything/#suppressing-nested-confirm-prompts
The answers presented here does not work for many cases and I see a danger in people implementing the answers here, without understanding the fundamentals.
Here is how a hacked it to work across scriptmodules: