This document describes the mandatory patterns for implementing confirmation support in TfsCmdlets cmdlets.
Every cmdlet that modifies state (verbs like New, Set, Remove, Enable, Disable, Update, Rename, Import, Move, etc.) must declare SupportsShouldProcess = true in its [TfsCmdlet] attribute. This enables -WhatIf and -Confirm support automatically.
[TfsCmdlet(CmdletScope.Collection, SupportsShouldProcess = true)]
partial class SetSomething { /* ... */ }All TfsCmdlets mutation cmdlets use the default ConfirmImpact of Medium. The user gets a confirmation prompt only when $ConfirmPreference is Medium or Low, or when they explicitly pass -Confirm.
In the controller, call ShouldProcess before executing the mutation:
if (!PowerShell.ShouldProcess(target, "Create something"))
yield break;Destructive or irreversible cmdlets (e.g. Remove-*, hard-delete operations, permanent revocations) must use a two-barrier approach:
ShouldProcess— standard PowerShell infrastructure barrier (respects-WhatIf,-Confirm, and$ConfirmPreference).ShouldContinue— operator-level safety barrier that requires explicit acknowledgment. Bypassed only by the-Forceswitch.
This prevents accidental destructive operations when a script or caller sets $ConfirmPreference = 'None' or passes -Confirm:$false.
In the cmdlet class, add a Force switch parameter:
[TfsCmdlet(CmdletScope.Collection, SupportsShouldProcess = true)]
partial class RemoveSomething
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
public object Item { get; set; }
/// <summary>
/// Suppresses the confirmation prompt for destructive operations.
/// </summary>
[Parameter]
public SwitchParameter Force { get; set; }
}In the controller, implement the gated two-barrier check:
protected override IEnumerable Run()
{
// 1st barrier: ShouldProcess (handles -WhatIf and -Confirm)
if (!PowerShell.ShouldProcess(target, "Delete item"))
yield break;
// 2nd barrier: ShouldContinue (operator safety, only when -Force is not specified)
if (!Force && !PowerShell.ShouldContinue(
$"Are you sure you want to delete '{target}'? This operation is irreversible."))
yield break;
// Perform the destructive operation
Client.DeleteAsync(itemId).Wait("Error deleting item");
}| Scenario | ShouldProcess | ShouldContinue | Operation runs? |
|---|---|---|---|
| Interactive, no switches | Prompts (if ConfirmImpact ≥ $ConfirmPreference) | Prompts | Only if both confirmed |
-WhatIf |
Returns false (shows "What if" message) |
Never reached | No |
-Confirm:$false |
Returns true silently |
Still prompts | Only if ShouldContinue confirmed |
-Force |
Prompts normally | Bypassed | If ShouldProcess confirmed |
-Confirm:$false -Force |
Returns true silently |
Bypassed | Yes (fully automated) |
Pipeline / $ConfirmPreference = 'None' |
Returns true silently |
Still prompts | Only if ShouldContinue confirmed |
Pipeline / $ConfirmPreference = 'None' + -Force |
Returns true silently |
Bypassed | Yes (fully automated) |
ShouldProcessis for the PowerShell engine. It respects$ConfirmPreference,-WhatIf, and-Confirm. It can be silenced by the caller or session state.ShouldContinueis for the operator. It always prompts interactively unless bypassed by-Force. It cannot be silenced by$ConfirmPreferenceor-Confirm:$false.- The
-Forceparameter is the only way to bypassShouldContinuein automation scripts. This ensures destructive operations in pipelines require explicit intent in the calling code. - Never call
ShouldContinuewithout gating it behind!Force— otherwise you create an unconditional interactive prompt that makes automation impossible. - In interactive scenarios,
ShouldProcessprompts when$ConfirmPreferenceisMediumor lower.ShouldContinuethen adds a second, distinct confirmation for the destructive nature of the operation. This is acceptable for truly irreversible operations.
- If the operation is a standard workflow step (e.g. creating a work item, setting a property),
ShouldProcessalone is sufficient. - Use the two-barrier pattern only when:
- The operation is destructive and irreversible (e.g. permanent deletion, revoking tokens, destroying resources).
- You need the user to read a specific warning that the generic
-Confirmprompt does not convey. - You want to require
-Forcein automation scripts to guarantee explicit intent.
The -Force + ShouldContinue barrier is only required for irreversible operations. Many Azure DevOps resources support a "recycle bin" concept — deleting them is a reversible (soft-delete) operation that can be undone. In those cases, ShouldProcess alone is sufficient; -Force is not needed.
However, when a cmdlet offers a hard-delete path (permanent, irreversible destruction), that specific path must require -Force and use ShouldContinue.
| Operation | Reversible? | Requires -Force? |
Example |
|---|---|---|---|
| Delete team project (soft) | Yes (recycle bin) | No | Remove-TfsTeamProject |
| Delete team project (hard) | No | Yes | Remove-TfsTeamProject -Hard |
| Delete work item | Yes (recycle bin) | No | Remove-TfsWorkItem |
| Destroy work item | No | Yes | Remove-TfsWorkItem -Destroy |
| Delete Git repository (empty) | Yes (recycle bin) | No | Remove-TfsGitRepository |
| Delete Git repository (non-empty) | Potentially destructive | Yes | Remove-TfsGitRepository (with content) |
| Revoke PAT | No | Yes | Remove-TfsPersonalAccessToken |
protected override IEnumerable Run()
{
// ShouldProcess always required for any mutation
if (!PowerShell.ShouldProcess(target, "Delete item"))
yield break;
if (Hard)
{
// Hard delete is irreversible — require Force + ShouldContinue
if (!Force && !PowerShell.ShouldContinue(
$"The item '{target}' will be permanently destroyed. " +
"This operation is IRREVERSIBLE and may cause DATA LOSS. Continue?"))
yield break;
Client.HardDeleteAsync(itemId).Wait("Error destroying item");
}
else
{
// Soft delete goes to recycle bin — ShouldProcess is sufficient
Client.SoftDeleteAsync(itemId).Wait("Error deleting item");
}
}The key principle: -Force signals explicit intent for irreversible actions. If the user (or a script) can undo the operation, the standard ShouldProcess confirmation is sufficient protection.
RemoveTeamProject— usesShouldProcess+ShouldContinuewith-Forcefor soft-delete, and an additionalShouldContinuefor hard-delete.RemoveWorkItem— usesShouldProcessfor standard delete,ShouldContinuegated by-Forcefor the-Destroy(permanent) path.RemoveGitRepository— usesShouldProcess+ShouldContinuegated by-Forcewhen the repository has a default branch (i.e. is not empty).