Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
As funções do PowerShell têm vários recursos que melhoram muito a maneira como os usuários interagem com elas.
Uma característica importante que muitas vezes é negligenciada é o suporte para -WhatIf e -Confirm, e é fácil adicioná-lo às suas funções. Neste artigo, nos aprofundamos em como implementar esse recurso.
Nota
A versão original deste artigo apareceu no blog escrito por @KevinMarquette. A equipe do PowerShell agradece Kevin por compartilhar esse conteúdo conosco. Por favor, confira seu blog em PowerShellExplained.com.
Este é um recurso simples que você pode habilitar em suas funções para fornecer uma rede de segurança para os usuários que precisam dela. Não há nada mais assustador do que executar um comando que você sabe que pode ser perigoso pela primeira vez. A opção de executá-lo com -WhatIf pode fazer uma grande diferença.
ParâmetrosComuns
Antes de analisarmos a implementação desses parâmetros comuns, quero dar uma olhada rápida em como eles são usados.
Usando -WhatIf
Quando um comando suporta o -WhatIf parâmetro, ele permite que você veja o que o comando teria feito em vez de fazer alterações. É uma boa maneira de testar o impacto de um comando, especialmente antes de fazer algo destrutivo.
PS C:\temp> Get-ChildItem
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 4/19/2021 8:59 AM 0 importantfile.txt
-a---- 4/19/2021 8:58 AM 0 myfile1.txt
-a---- 4/19/2021 8:59 AM 0 myfile2.txt
PS C:\temp> Remove-Item -Path .\myfile1.txt -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
Se o comando for implementado ShouldProcesscorretamente, ele deverá mostrar todas as alterações que ele teria feito. Aqui está um exemplo usando um curinga para excluir vários arquivos.
PS C:\temp> Remove-Item -Path * -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
What if: Performing the operation "Remove File" on target "C:\Temp\myfile2.txt".
What if: Performing the operation "Remove File" on target "C:\Temp\importantfile.txt".
Usando -Confirm
Os comandos que suportam -WhatIf também suportam -Confirm. Isso lhe dá a chance de confirmar uma ação antes de executá-la.
PS C:\temp> Remove-Item .\myfile1.txt -Confirm
Confirm
Are you sure you want to perform this action?
Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
Nesse caso, você tem várias opções que permitem continuar, ignorar uma alteração ou parar o script. O prompt de ajuda descreve cada uma dessas opções como esta.
Y - Continue with only the next step of the operation.
A - Continue with all the steps of the operation.
N - Skip this operation and proceed with the next operation.
L - Skip this operation and all subsequent operations.
S - Pause the current pipeline and return to the command prompt. Type "exit" to resume the pipeline.
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
Localização
Esse prompt é localizado no PowerShell para que o idioma seja alterado com base no idioma do seu sistema operacional. Essa é mais uma coisa que o PowerShell gerencia para você.
[switch] Parâmetros
Vamos parar um momento rápido para ver formas de passar um valor a um [switch] parâmetro. A principal razão pela qual aponto isso é que muitas vezes queres passar valores de parâmetro para as funções que chamas.
A primeira abordagem é uma sintaxe específica de parâmetros que pode ser usada para todos os parâmetros, mas que normalmente se vê usada para [switch] parâmetros. Você especifica dois pontos para anexar um valor ao parâmetro.
Remove-Item -Path:* -WhatIf:$true
Você pode fazer o mesmo com uma variável.
$DoWhatIf = $true
Remove-Item -Path * -WhatIf:$DoWhatIf
A segunda abordagem é usar uma tabela hash para espalhar o valor.
$RemoveSplat = @{
Path = '*'
WhatIf = $true
}
Remove-Item @RemoveSplat
Se você é novo em hashtables ou splatting, tenho outro artigo sobre isso que cobre tudo o que você queria saber sobre hashtables.
SuportaDeveProcessar
**
O primeiro passo para habilitar o suporte a -WhatIf e -Confirm é especificar SupportsShouldProcess na CmdletBinding da sua função.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
Remove-Item .\myfile1.txt
}
Especificando SupportsShouldProcess desta forma, podemos agora chamar a nossa função com -WhatIf (ou -Confirm).
PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Remove File" on target "C:\Temp\myfile1.txt".
Observe que eu não criei um parâmetro chamado -WhatIf. A especificação de SupportsShouldProcess cria-o automaticamente para nós. Quando especificamos o -WhatIf parâmetro em Test-ShouldProcess, algumas das funcionalidades que invocamos também executam o processamento -WhatIf.
Nota
Ao utilizar SupportsShouldProcess, o PowerShell não adiciona a variável $WhatIf à função. Você não precisa verificar o valor de $WhatIf porque o ShouldProcess() método cuida disso para você.
Confiar, mas verificar
Há algum perigo em acreditar que tudo a que se chama herda os valores -WhatIf. Para o resto dos exemplos, vou assumir que não funciona e ser muito explícito ao fazer chamadas para outros comandos. Eu recomendo que você faça o mesmo.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
Remove-Item .\myfile1.txt -WhatIf:$WhatIfPreference
}
Vou revisitar as nuances muito mais tarde, uma vez que você tenha uma melhor compreensão de todas as peças em jogo.
$PSCmdlet.ShouldProcess
O método que permite implementar SupportsShouldProcess é $PSCmdlet.ShouldProcess. Você chama $PSCmdlet.ShouldProcess(...) para ver se deve processar a lógica e o PowerShell cuida do resto. Vamos começar com um exemplo:
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
$file = Get-ChildItem './myfile1.txt'
if($PSCmdlet.ShouldProcess($file.Name)){
$file.Delete()
}
}
A chamada para $PSCmdlet.ShouldProcess($file.Name) verifica o -WhatIf (e o parâmetro -Confirm) e, em seguida, manipula-o de acordo. O -WhatIf faz com que ShouldProcess produza uma descrição da alteração e devolva $false.
PS> Test-ShouldProcess -WhatIf
What if: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
Uma chamada usando -Confirm pausa o script e solicita ao usuário a opção de continuar. Ele retorna $true se o usuário selecionou Y.
PS> Test-ShouldProcess -Confirm
Confirm
Are you sure you want to perform this action?
Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
Uma funcionalidade impressionante do $PSCmdlet.ShouldProcess é que ele também serve como saída detalhada. Eu dependo disso muitas vezes ao implementar ShouldProcess.
PS> Test-ShouldProcess -Verbose
VERBOSE: Performing the operation "Test-ShouldProcess" on target "myfile1.txt".
Sobrecargas
Existem algumas sobrecargas diferentes para $PSCmdlet.ShouldProcess com diferentes parâmetros para personalizar as mensagens. Já vimos o primeiro no exemplo acima. Vamos dar uma olhada mais de perto.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
if($PSCmdlet.ShouldProcess('TARGET')){
# ...
}
}
Isso produz uma saída que inclui o nome da função e o destino (valor do parâmetro).
What if: Performing the operation "Test-ShouldProcess" on target "TARGET".
Ao especificar a operação como segundo parâmetro, o valor da operação é utilizado em vez do nome da função na mensagem.
## $PSCmdlet.ShouldProcess('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".
A próxima opção é especificar três parâmetros para personalizar totalmente a mensagem. Quando três parâmetros são usados, o primeiro é a mensagem inteira. Os segundos dois parâmetros ainda são usados na saída da mensagem -Confirm.
## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE
Referência rápida de parâmetros
Apenas no caso de você vir aqui apenas para descobrir quais parâmetros você deve usar, aqui está uma referência rápida mostrando como os parâmetros mudam a mensagem nos diferentes -WhatIf cenários.
## $PSCmdlet.ShouldProcess('TARGET')
What if: Performing the operation "FUNCTION_NAME" on target "TARGET".
## $PSCmdlet.ShouldProcess('TARGET','OPERATION')
What if: Performing the operation "OPERATION" on target "TARGET".
## $PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION')
What if: MESSAGE
Eu tendo a usar aquele com dois parâmetros.
ShouldProcessReason
Temos uma quarta sobrecarga que é mais avançada do que as outras. Ele permite que você obtenha o motivo pelo qual ShouldProcess foi executado. Eu só estou adicionando isso aqui para completar porque podemos apenas verificar se $WhatIfPreference é $true em vez disso.
$reason = ''
if($PSCmdlet.ShouldProcess('MESSAGE','TARGET','OPERATION',[ref]$reason)){
Write-Output "Some Action"
}
$reason
Temos que passar a $reason variável para o quarto parâmetro como uma variável de referência com [ref].
ShouldProcess preenche $reason com o valor None ou WhatIf. Eu não disse que isso era útil e eu não tive nenhuma razão para usá-lo.
Onde colocá-lo
Você usa ShouldProcess para tornar seus scripts mais seguros. Assim, você o usa quando seus scripts estão fazendo alterações. Gosto de colocar a $PSCmdlet.ShouldProcess chamada o mais próximo possível da mudança.
## general logic and variable work
if ($PSCmdlet.ShouldProcess('TARGET','OPERATION')){
# Change goes here
}
Se estiver a processar uma coleção de itens, invoco-o para cada item. Assim, a chamada é colocada dentro do foreach loop.
foreach ($node in $collection){
# general logic and variable work
if ($PSCmdlet.ShouldProcess($node,'OPERATION')){
# Change goes here
}
}
A razão pela qual eu coloco ShouldProcess firmemente em torno da mudança, é que eu quero o máximo de código para executar quanto possível quando -WhatIf é especificado. Quero que a configuração e a validação sejam executadas, se possível, para que o usuário veja esses erros.
Eu também gosto de usar isso em meus testes Pester que validam meus projetos. Se eu tenho um pedaço de lógica que é difícil de simular em Pester, muitas vezes posso envolvê-lo em ShouldProcess e chamá-lo com -WhatIf nos testes. É melhor testar parte do seu código do que nenhum.
$WhatIfPreference
A primeira variável de preferência que temos é $WhatIfPreference. Isso ocorre $false por padrão. Se você defini-lo para $true então sua função é executada como se você especificou -WhatIf. Se isto for definido na sua sessão, todos os comandos executarão -WhatIf.
Quando você chama uma função com -WhatIf, o valor de $WhatIfPreference fica definido como $true dentro do escopo da sua função.
ConfirmarImpacto
A maioria dos meus exemplos são para -WhatIf , mas tudo até agora também funciona com -Confirm para avisar o usuário. Você pode configurar o ConfirmImpact da função para alto, e ele solicita ao usuário como se tivesse sido chamado com -Confirm.
function Test-ShouldProcess {
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param()
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
}
Esta chamada para Test-ShouldProcess está a executar a ação -Confirm devido ao impacto High.
PS> Test-ShouldProcess
Confirm
Are you sure you want to perform this action?
Performing the operation "Test-ShouldProcess" on target "TARGET".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): y
Some Action
A questão óbvia é que agora é mais difícil usar em outros scripts sem avisar o usuário. Neste caso, podemos passar um $false para -Confirm para suprimir o prompt.
PS> Test-ShouldProcess -Confirm:$false
Some Action
Abordarei como adicionar -Force suporte em uma seção posterior.
$ConfirmPreference
$ConfirmPreference é uma variável automática que controla quando ConfirmImpact lhe pede para confirmar a execução. Aqui estão os valores possíveis para ambos $ConfirmPreference e ConfirmImpact.
HighMediumLowNone
Com esses valores, você pode especificar diferentes níveis de impacto para cada função. Se você tiver $ConfirmPreference definido para um valor maior que ConfirmImpact, então você não será solicitado a confirmar a execução.
Por padrão, $ConfirmPreference está definido como High e ConfirmImpact é Medium. Para que o utilizador seja automaticamente solicitado pela função, defina o seu ConfirmImpact como High. Caso contrário, defina-o para Medium se for destrutivo e use Low se o comando for sempre seguro para execução em produção. Ao definir como none, ele não irá solicitar confirmação mesmo se -Confirm for especificado, mas ainda oferece suporte a -WhatIf.
Ao chamar uma função com -Confirm, o valor de $ConfirmPreference fica definido como Low dentro do escopo da sua função.
Suprimir prompts de confirmação aninhados
O $ConfirmPreference pode ser captado por funções que você chama. Isso pode criar cenários em que se adiciona um prompt de confirmação e a função chamada também solicita uma resposta ao utilizador.
O que eu costumo fazer é especificar -Confirm:$false nos comandos que eu chamo quando já tratei do prompting.
function Test-ShouldProcess {
[CmdletBinding(SupportsShouldProcess)]
param()
$file = Get-ChildItem './myfile1.txt'
if($PSCmdlet.ShouldProcess($file.Name)){
Remove-Item -Path $file.FullName -Confirm:$false
}
}
Isso nos leva de volta a um aviso anterior: há nuances sobre quando -WhatIf não é passado para uma função e quando -Confirm passa para uma função. Prometo que voltarei a isso mais tarde.
$PSCmdlet.DeveContinuar
Se precisar de mais controlo do que ShouldProcess providencia, pode acionar o prompt diretamente com ShouldContinue.
ShouldContinue ignora $ConfirmPreference, ConfirmImpact, -Confirm, $WhatIfPreference e -WhatIf porque solicita sempre que é executado.
Em um rápido relance, é fácil confundir ShouldProcess e ShouldContinue. Eu costumo lembrar-me de usar ShouldProcess porque o parâmetro é chamado SupportsShouldProcess no CmdletBinding.
Você deve usar ShouldProcess em quase todos os cenários. Foi por isso que abordei primeiro esse método.
Vamos ver ShouldContinue em ação.
function Test-ShouldContinue {
[CmdletBinding()]
param()
if($PSCmdlet.ShouldContinue('TARGET','OPERATION')){
Write-Output "Some Action"
}
}
Isso nos fornece um prompt mais simples com menos opções.
Test-ShouldContinue
OPERATION
TARGET
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"):
O maior problema com ShouldContinue é que ele exige que o utilizador o execute interativamente, porque ele sempre solicita ao utilizador. Você deve sempre criar ferramentas que podem ser usadas por outros scripts. A maneira de fazer isso é implementando -Force. Voltarei a esta ideia mais tarde.
Sim a todos
Isso é tratado automaticamente com ShouldProcess, mas temos que fazer um pouco mais de trabalho para ShouldContinue. Há uma segunda sobrecarga de método onde temos que passar alguns valores por referência para controlar a lógica.
function Test-ShouldContinue {
[CmdletBinding()]
param()
$collection = 1..5
$yesToAll = $false
$noToAll = $false
foreach($target in $collection) {
$continue = $PSCmdlet.ShouldContinue(
"TARGET_$target",
'OPERATION',
[ref]$yesToAll,
[ref]$noToAll
)
if ($continue){
Write-Output "Some Action [$target]"
}
}
}
Adicionei um foreach loop e uma coleção para os mostrar em ação. Puxei a ShouldContinue chamada da if declaração para facilitar a leitura. Chamar um método com quatro parâmetros começa a ficar um pouco feio, mas eu tentei fazê-lo parecer o mais limpo possível.
Implementar -Force
ShouldProcess e ShouldContinue precisam implementar -Force de diferentes maneiras. O truque para essas implementações é que ShouldProcess sempre deve ser executado, mas ShouldContinue não deve ser executado se -Force for especificado.
DeveProcessar -Força
Se você definir o seu ConfirmImpact como high, a primeira coisa que seus usuários vão tentar é suprimi-lo com -Force. Essa é a primeira coisa que faço de qualquer maneira.
Test-ShouldProcess -Force
Error: Test-ShouldProcess: A parameter cannot be found that matches parameter name 'force'.
Se te lembrares da ConfirmImpact seção, eles realmente precisam chamá-lo desta forma:
Test-ShouldProcess -Confirm:$false
Nem todo mundo percebe que precisa fazer isso e -Force não reprime ShouldContinue.
Portanto, devemos implementar -Force para a sanidade de nossos usuários. Dê uma olhada neste exemplo completo aqui:
function Test-ShouldProcess {
[CmdletBinding(
SupportsShouldProcess,
ConfirmImpact = 'High'
)]
param(
[switch]$Force
)
if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
$ConfirmPreference = 'None'
}
if ($PSCmdlet.ShouldProcess('TARGET')) {
Write-Output "Some Action"
}
}
Adicionamos o nosso próprio -Force interruptor como parâmetro. O -Confirm parâmetro é adicionado automaticamente ao usar SupportsShouldProcess no CmdletBinding. No entanto, quando se utiliza SupportsShouldProcess, o PowerShell não adiciona a variável $Confirm à função. Se você estiver executando no modo estrito e tentar usar a $Confirm variável antes que ela tenha sido definida, você receberá um erro. Para evitar o erro, você pode usar $PSBoundParameters para testar se o parâmetro foi passado pelo usuário.
if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
$ConfirmPreference = 'None'
}
Se o usuário especificar -Force , definimos $ConfirmPreference como None no escopo local. Se o usuário também especificar -Confirm , então ShoudProcess() honrará os valores do -Confirm parâmetro.
if ($PSCmdlet.ShouldProcess('TARGET')){
Write-Output "Some Action"
}
Se alguém especificar ambos -Force e -WhatIf, então -WhatIf precisa ter prioridade. Essa abordagem preserva o -WhatIf processamento porque ShouldProcess sempre é executado.
Não adicione um teste de valor $Force na instrução if usando o ShouldProcess. Esse é um anti-padrão para este cenário específico, embora seja isso que mostro na próxima seção para ShouldContinue.
DeveContinuar -Força
Esta é a maneira correta de implementar -Force com ShouldContinue.
function Test-ShouldContinue {
[CmdletBinding()]
param(
[switch]$Force
)
if($Force -or $PSCmdlet.ShouldContinue('TARGET','OPERATION')){
Write-Output "Some Action"
}
}
Ao colocar o $Force à esquerda do -or operador, ele é avaliado primeiro. Escrevê-lo desta forma provoca um curto-circuito na execução da if declaração. Caso $Force seja $true, então o ShouldContinue não é executado.
PS> Test-ShouldContinue -Force
Some Action
Não temos que nos preocupar com -Confirm ou -WhatIf neste cenário porque eles não são suportados pela ShouldContinue. É por isso que tem de ser tratado de forma diferente do ShouldProcess.
Questões relativas ao âmbito de aplicação
Usando -WhatIf e -Confirm devem ser aplicados a tudo dentro das funções e a tudo o que essas funções chamam. Eles fazem isso definindo $WhatIfPreference para $true ou definindo $ConfirmPreference para Low no escopo local da função. Quando se chama outra função, as chamadas para ShouldProcess usam esses valores.
Isso realmente funciona corretamente na maioria das vezes. Sempre que chamar um cmdlet interno ou uma função dentro do mesmo escopo, isso funcionará. Ele também funciona quando você chama um script ou uma função em um módulo de script do console.
O único lugar específico onde ele não funciona é quando um script ou um módulo de script chama uma função em outro módulo de script. Isso pode não parecer um grande problema, mas a maioria dos módulos que você cria ou extrai do PSGallery são módulos de script.
A questão central é que os módulos de script não herdam os valores para $WhatIfPreference ou $ConfirmPreference (e vários outros) quando chamados de funções em outros módulos de script.
A melhor maneira de resumir isso como uma regra geral é que isso funcione corretamente para módulos binários e nunca confie nele para funcionar para módulos de script. Se você não tiver certeza, teste-o ou simplesmente assuma que ele não funciona corretamente.
Eu sinto que isso é muito perigoso porque cria cenários em que se adiciona -WhatIf suporte a vários módulos que funcionam corretamente de forma isolada, mas falham em funcionar corretamente quando se chamam uns aos outros.
Temos um RFC no GitHub a trabalhar para resolver este problema. Consulte Propagar preferências de execução além do escopo do módulo de script para obter mais detalhes.
Para finalizar
Eu tenho que procurar como usar ShouldProcess cada vez que eu preciso usá-lo. Demorei muito tempo a distinguir ShouldProcess de ShouldContinue. Eu quase sempre preciso procurar quais parâmetros usar. Portanto, não se preocupe se você ainda ficar confuso de vez em quando. Este artigo estará aqui quando você precisar. Tenho certeza de que farei referência a ele muitas vezes.
Se você gostou deste post, por favor, compartilhe seus pensamentos comigo no Twitter usando o link abaixo. Eu sempre gosto de ouvir de pessoas que obtêm valor do meu conteúdo.