I'm obviously trying to do something wrong, so hoping someone can help since it seems easy and it is probably an easy mistake. Basically I am writing a little script to create new folders and I want to include -Confirm
, but I want it to basically ask ONCE if the user wants to continue, instead of asking for each nested folder that is created.
Here's what I have so far
function Create-NewFolderStructure{
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory)]
[string]$NewFolderName,
[Parameter()]
$LiteralPath = (Get-Location),
[Parameter()]
$inputFile = "zChildDirectoryNames.txt"
)
process {
$here = $LiteralPath
$directories = Get-Content -LiteralPath "$($here)\$($inputFile)"
If (-not (Test-Path -Path "$($here)\$($NewFolderName)")){
$directories | ForEach-Object {New-Item -ItemType "directory" -Path "$($here)\$($NewFolderName)" -Name $_}
}
Else {
Write-Host "A $($NewFolderName) folder already exists"
}
}
end {
}
}
The issue is that if I use -Confirm
when calling my function, or directly at New-Item
- I end up getting the confirmation prompt for every single 'child folder' that is being created. Even with the first prompt, saying "Yes to all" doesn't suppress future prompts
CodePudding user response:
In order to consume the user's answer to the confirmation prompt, you have to actually ask them!
You can do so by calling $PSCmdlet.ShouldContinue()
:
function Create-NewFolderStructure {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[string]$NewFolderName,
[Parameter()]
$LiteralPath = (Get-Location),
[Parameter()]
$inputFile = "zChildDirectoryNames.txt",
[switch]$Force
)
process {
if(-not $Force){
$yesToAll = $false
$noToAll = $false
if($PSCmdlet.ShouldContinue("Do you want to go ahead and create new directory '${NewFolderName}'?", "Danger!", [ref]$yesToAll, [ref]$noToAll)){
if($yesToAll){
$Force = $true
}
}
else{
return
}
}
# Replace with actual directory creation logic here,
# pass `-Force` or `-Confirm:$false` to New-Item to suppress its own confirmation prompt
Write-Host "Creating new folder ${NewFolderName}" -ForegroundColor Red
}
}
Now, if the user presses [A]
("Yes To All"), we'll remember it by setting $Force
to true, which will skip any subsequent calls to ShouldContinue()
:
PS ~> "folder 1","folder 2"|Create-NewFolderStructure
Danger!
Do you want to go ahead and create new directory 'folder 1'?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): A
Creating new folder folder 1
Creating new folder folder 2
PS ~>
I'd strongly suggest reading through this deep dive to better understand the SupportsShouldProcess
facility: Everything you wanted to know about ShouldProcess
CodePudding user response:
Mathias R. Jessen's helpful answer provides an alternative to using the common -Confirm
parameter, via a slightly different mechanism based on a custom -Force
switch that inverts the logic of your own attempt (prompt is shown by default, can be skipped with -Force
).
If you want to make your -Confirm
-based approach work:
Call
.ShouldProcess()
yourself at the start of theprocess
block, which presents the familiar confirmation prompt, ...... and, if the method returns
$true
- indicating that the use confirmed - perform the desired operations after setting a local copy of the$ConfirmPreference
preference variable to'None'
, so as to prevent the cmdlets involved in your operations to each prompt as well.
function New-FolderStructure {
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[string] $NewFolderName,
[string] $LiteralPath = $PWD,
[string] $InputFile = "zChildDirectoryNames.txt"
)
# This block is called once, before pipeline processing begins.
begin {
$directories = Get-Content -LiteralPath "$LiteralPath\$InputFile"
}
# This block is called once if parameter $NewFolderName is bound
# by an *argument* and once for each input string if bound via the *pipeline*.
process {
# If -Confirm was passed, this will prompt for confirmation
# and return `$true` if the user confirms.
# Otherwise `$true` is automatically returned, which also happens
# if '[A] Yes to All' was chosen for a previous input string.
if ($PSCmdlet.ShouldProcess($NewFolderName)) {
# Prevent the cmdlets called below from *also* prompting for confirmation.
# Note: This creates a *local copy* of the $ConfirmPreference
# preference variable.
$ConfirmPreference = 'None'
If (-not (Test-Path -LiteralPath "$LiteralPath\$NewFolderName")) {
$directories | ForEach-Object { New-Item -ItemType Directory -Path "$LiteralPath\$NewFolderName" -Name $_ }
}
Else {
Write-Host "A $NewFolderName folder already exists"
}
}
}
}
Note:
I've changed your function's name from
Create-NewFolderStructure
toNew-FolderStructure
to comply with PowerShell's naming conventions, using the approved "verb"New
and streamlined the code in a number of ways.ValueFromPipeline
was added as a property to the$NewFolderName
parameter so as to support passing multiple values via the pipeline (e.g.
'foo', 'bar' | New-FolderStructure -Confirm
)- Note that the
process
block is then called for each input string, and will also prompt for each, unless you respond with[A] Yes to All
); PowerShell remembers this choice betweenprocess
calls and makes.ShouldProcess()
automatically return$true
in subsequent calls; similarly, responding to[L] No to All
automatically returns$false
in subsequent calls.
- Note that the
The need to set
$ConfirmPreference
to'None'
stems from the fact that PowerShell automatically translates the caller's use of-Confirm
into a function-local$ConfirmPreference
variable with value'Low'
, which makes all cmdlets that support-Confirm
act as if-Confirm
had been passed..ShouldProcess()
does not pick up in-function changes to the value of this$ConfirmPreference
copy, so it is OK to set it to'None'
, without affecting the prompt logic of subsequentprocess
invocations.