Seriál Windows Powershell: Funkce 2 (část 12.)

V minulém díle jsme se plně věnovali funkcím. I když jsem chtěl dnes navázat tématem skripty a moduly, rozhodl jsem se téma funkcí ještě trochu rozšířit použitím atributu CmdletBinding. Vzhledem k tomu, že funkce považuji za základní stavební kameny pokročilejšího skriptování, nemyslím, že by to bylo na škodu. O skriptech a modulech si tedy povíme až v některém z příštích pokračování.

Opravdu pokročilé funkce

Použití [CmdletBinding()]

Možná už jste četli tématickou nápovědu about_Common_Parameters a slyšeli jste, že je možné tyto parametry využívat ve vašich funkcích. Malá zmínka je v další tématické nápovědě (about_Functions_CmdletBindingAttribute) a tato dale odkazuje na MSDN, kde se můžete dočíst více. Abyste se nemuseli prokousávat celou dokumentací, ukážeme si jednoduché použití atributu CmdletBinding.

Mějme základní “tupou” funkci.

PS C:\> function secti {param($a,$b) $a+$b}
PS C:\> secti 5 6
11

Zjistíme, jaké má funkce parametry:

PS C:\> (gcm secti).Parameters | select -expand Keys
a
b

Z minulého dílu víme, že můžeme do funkce přidat nápovědu, validaci parametrů, atd., ale pořád nevidíme žádný z “common parameters”. Přidejme tedy atribut CmdletBinding.

PS C:\> function secti {[CmdletBinding()] param($a,$b) $a+$b}
PS C:\> secti 1 5
6

PS C:\> (gcm secti).Parameters | select -expand Keys
a
b
Verbose
Debug
ErrorAction
WarningAction
ErrorVariable
WarningVariable
OutVariable
OutBuffer

Bez jakékoli větší práce nám tvůrci PowerShellu dodali další zajímavé parametry.

Pozor: Tímto jsme nevytvořili vlastní cmdlet. Stále se jedná o funkci. Cmdlety jsou odvozené od tříd Cmdlet nebo PsCmdlet. Možno ověřit např. i následujícím způsobem:

PS C:\> (Get-Command Get-Command).GetType().FullName
System.Management.Automation.CmdletInfo

PS C:\> (Get-Command secti).CmdletBinding
True

PS C:\> (Get-Command secti).GetType().FullName
System.Management.Automation.FunctionInfo

Více k CmdletInfo a FunctionInfo lze dohledat opět na MSDN.

Vytvořme si novou funkci, která bude opět sčítat dvě čísla.

function Get-Soucet
{
[CmdletBinding()]
param (
[int]$a = 1,
[int]$b = 1
)

Write-Verbose "Vstupni cislo a: $a"
Write-Verbose "Vstupni cislo b: $b"

Write-Verbose "Pred scitanim."
$soucet = $a + $b
Write-Verbose "Secteno jako: $soucet"

Write-Host "Soucet cisel $a a $b je $soucet."

Write-Verbose "Konec funkce."
}

Povšimněte si použití Write-Verbose. Jedná se o naše vlastní “kontrolní” výpisy, kterými můžeme zjišťovat stav funkce v průběhu jejího běhu.

PS C:\Scripts> Get-Soucet
Soucet cisel 1 a 1 je 2.
PS C:\Scripts> Get-Soucet 5 5
Soucet cisel 5 a 5 je 10.
PS C:\Scripts> Get-Soucet 5 5 -Verbose
VERBOSE: Vstupni cislo a: 5
VERBOSE: Vstupni cislo b: 5
VERBOSE: Pred scitanim.
VERBOSE: Secteno jako: 10
Soucet cisel 5 a 5 je 10.
VERBOSE: Konec funkce.

Vidíte, že při použití parametru Verbose jsme dostali navíc informace, které se nám mohou hodit v průběhu ladění. Pokud si tuto techniku zapamatujete, odpadne vám běžný problem se zadáváním Write-Host na různá místa a poté jeho odstraňování.

Chcete opravdu zmáčknout to červené tlačítko?

Pro někoho jsou možná dialogová okna s nápisy – “Are you sure you want to …” (doplňte dle libosti) – otravná, ale zřejmě pouze do doby, než jsou na konci okna slova “doména” a “smazat” nebezpečně blízko u sebe. Administrace v PowerShellu (nebo spíše obecně v příkazové řádce, skriptu, …) je skvělá, rychlá, méně náchylná na lidské chyby, ale má samozřejmě svá rizika. Například spojení cmdletů Get-ADUser a Remove-ADUser na jedná řádce vám může bez dobrého filtrování způsobit docela těžkou hlavu (poděkujme za Enable-ADOptionalFeature).

Možnost vložit so svých skriptů dotaz na provedení akce se hodí při jakékoli destruktivní operaci. PowerShell v2 nám toto umožňuje, pojďme se podívat jak na to. Nejdříve si ukážeme celou funkci a poté vysvětlíme jak funguje.

function Invoke-Phobia
{
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[int]$cislo,
[switch]$mocnina
)

"Soucet: $($cislo+$cislo)"
if ($mocnina)
{
if ($PsCmdlet.ShouldProcess("$cislo", "Pata mocnina"))
{
"Mocnina $([Math]::Pow($cislo, 5))"
}
}
}

Jak vidíte, přidali jsme parametr SupportsShouldProcess. Funkce nám okamžitě nabízí dva nové parametry – WhatIf a Confirm.

PS C:\> (gcm Invoke-Phobia).Parameters | select -expand Keys
cislo
mocnina
Verbose
Debug
ErrorAction
WarningAction
ErrorVariable
WarningVariable
OutVariable
OutBuffer
WhatIf
Confirm

Tyto parametry lze použít u velké části cmdletů, takže například

PS C:\> Remove-Item *.ps1 -WhatIf
What if: Performing operation "Remove File" on Target "C:\Scripts\profile.ps1".
What if: Performing operation "Remove File" on Target "C:\Scripts\VeryVERYuseful-DO_NOT_DELETE.ps1".

nám dá šanci zareagovat při nesprávném použití. A teď zpět k naší funkci – provádí dvě operace, jednu nedestruktivní (součet – provádí se při každém volání) a jednu destruktivní (pátou mocninu – provede se pouze, pokud uvedeme switch mocnina). Destruktivní proto, že náš klient má fobii na vysoká čísla a mocnina by ho mohla rozrušit. Pojďme si tedy naši funkci vyzkoušet.

PS C:\> Invoke-Phobia 5
Soucet: 10

PS C:\> Invoke-Phobia 5 -WhatIf
Soucet: 10

PS C:\> Invoke-Phobia 5 -mocnina
Soucet: 10
Mocnina 3125

PS C:\> Invoke-Phobia 5 -mocnina -WhatIf
Soucet: 10
What if: Performing operation "Pata mocnina" on Target "5".

PS C:\> Invoke-Phobia 5 -mocnina -Confirm
Soucet: 10

Confirm
Are you sure you want to perform this action?
Performing operation "Pata mocnina" on Target "5".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):
Mocnina 3125

Při prvním volání dostaneme pouhý součet hodnoty. Stejně tak se nic převratného nestane, pokud použijeme parametr WhatIf bez volání mocniny. Zajímavá je pro nás až třetí varianta – volání mocniny a zaroveň uvedení WhatIf. PowerShell nám ukáže, co by se mohlo stát v případě, ze funkci spustíme bez WhatIf. Všimněte si, že parametry, které jsme uváděli u metody ShouldProcess se nám objevily ve výpisu. Použití Confirm je poté již jen rutinní záležitostí.

Použití WhatIf při každém volání

Představte si situaci, kdy pracujete se skutečně kritickým systémem a chcete mít jistotu, že v předchozích odstavcích zmiňované „bezpečnostní mechanismy“ jsou spouštěné při každém volání „destruktivního“ cmdletu nebo funkce.

C:\Script>ls | select name | fw -c 5
file1.txt file10.txt file2.txt file3.txt file4.txt
file5.txt file6.txt file7.txt file8.txt file9.txt

C:\Scripts>Remove-Item * -WhatIf
What if: Performing operation "Remove File" on Target "C:\Scripts\file1.txt".
What if: Performing operation "Remove File" on Target "C:\Scripts\file10.txt".
What if: Performing operation "Remove File" on Target "C:\Scripts\file2.txt".
(...)

C:\Scripts>Remove-Item *
What if: Performing operation "Remove File" on Target "C:\Scripts\file1.txt".
What if: Performing operation "Remove File" on Target "C:\Scripts\file10.txt".
What if: Performing operation "Remove File" on Target "C:\Scripts\file2.txt".
(...)

C:\Scripts>Remove-Item * -Force
What if: Performing operation "Remove File" on Target "C:\Scripts\file1.txt".
What if: Performing operation "Remove File" on Target "C:\Scripts\file10.txt".
What if: Performing operation "Remove File" on Target "C:\Scripts\file2.txt".
(...)

C:\Scripts>ls | select name | fw -c 5
file1.txt file10.txt file2.txt file3.txt file4.txt
file5.txt file6.txt file7.txt file8.txt file9.txt

Jak vidíte ani parametrem Force se nám nepodařilo donutit cmdlet Remove-Item, aby smazal dané soubory. Což by se nám mohlo hodit, pokud bychom se pohybovali např. o adresář výš, než jsme nyní. V PowerShellu existuje proměnná, která se jmenuje WhatIfPreference a určuje chování parametru WhatIf.

C:\Scripts>ls Variable:\WhatIfPreference | fl *
PSPath : Microsoft.PowerShell.Core\Variable::WhatIfPreference
PSDrive : Variable
PSProvider : Microsoft.PowerShell.Core\Variable
PSIsContainer : False
Name : WhatIfPreference
Description : If true, WhatIf is considered to be enabled for all commands.
Value : False
Visibility : Public
Module :
ModuleName :
Options : None
Attributes : {}

Z parametru Description můžeme vyčíst, jak tato proměnná funguje. Standardně je nastavena na False a ve všech předchozích případech jsem ji ručně nastavil na True.

C:\Scripts>Remove-Item *
What if: Performing operation "Remove File" on Target "C:\Scripts\file1.txt".
What if: Performing operation "Remove File" on Target "C:\Scripts\file10.txt".
What if: Performing operation "Remove File" on Target "C:\Scripts\file2.txt".
(...)

C:\Scripts>$WhatIfPreference
True

C:\Scripts>$WhatIfPreference = $false
C:\Scripts>Remove-Item *
C:\Scripts>ls | select name | fw -c 5

Poznámka: Pokud chcete některému z kolegů trošku ztížit práci v PowerShellu, myslím že vložení řádky $WhatIfPreference = $true do jeho profilu může být docela legrace. Jestli nečte TechNet Flash, tak se možná na chvilku zapotí J

Samozřejmě by nedávalo smysl, abyste před každým voláním Remove-Item (a podobných) vraceli hodnotu proměnné WhatIfPreference zpátky na false. Pomocí následujícího zápisu změníte hodnotu jednorázově.

C:\Scripts>$WhatIfPreference
True

C:\Scripts>Remove-Item * -WhatIf:$False
C:\Scripts>ls
C:\Scripts>

Tímto bychom ukončili naši miniexkurzi do tajů funkcí. Pokud vás zaujalo použití proměnné WhatIfPreference doporučuji k přečtení tématickou nápovědu about_preference_variables.

- David Moravec