Základní konstrukce při práci s funkcemi

Až do minulého dílu jsme se věnovali primárně práci v konzoli. S tou si vystačíme pro většinu administrátorských úkonů. Pokud ale budeme PowerShell využívat pro složitější zprávu, bude se nám časem hodit možnost psaní skriptů. Dnes si řekneme něco o funkcích. Budeme je považovat za základní stavební jednotku skriptu. Funkce můžete psát velmi krátké pro jednostranné použití nebo složitější např. pro zpracování objektů z roury. Takzvané advanced funkce nám například umožní jednoduché vložení nápovědy. Začněme tedy nějakou minimalistickou funkcí.

PS C:\ function Get-FreeDiskSpace { (gwmi win32_logicaldisk -filter 'DeviceID="C:"').freespace/1GB }
PS C:\ Get-FreeDiskSpace
196.306255340576

Nadefinovali jsme si funkci, která nám po zavolání vrátí volné místo na disku C. Za klíčovým slovem function uvedeme jméno funkce a poté ve složených závorkách tělo funkce. Zatím nic světoborného, pojďme do funkce přidat nějaké vstupní parametry. Stejně jako v předchzím případě můžete text celé funkce zapsat přímo do konzole nebo použít některý z editorů, např. PowerShell ISE. Editor nám přináší (mimojiné) výhodu zvýrazňování syntaxe a tím i snadnější hledání chyb.

function Get-FreeDiskSpace
{
param
(
  $drive = 'C:'
)
  $filter = 'DeviceID="' + $drive + '"'
  (Get-WmiObject Win32_LogicalDisk -filter $filter).FreeSpace/1GB
}

Poznámka: Čtenáře už by neměla překvapit konstrukce “1GB”.

Přidali jsme klíčové slovo param a použili pro parametr jméno drive. Nyní můžeme volit, na jakém disku nás volné místo zajímá, přičemž standardní hodnota je disk C.

PS C:\> Get-FreeDiskSpace
196.615299224854
PS C:\> Get-FreeDiskSpace C:
196.615299224854
PS C:\> Get-FreeDiskSpace D:
0
PS C:\> Get-FreeDiskSpace -drive C:
196.615299224854

V prvním případě voláme funkci bez parametrů a vráceno je množství volného místa na disku C, stejně jako ve druhém případě, kde jsme – trochu zbytečně – uvedli jméno disku. Třetí příklad na mém počítači vrátí nulu (disk D není v systému). Ve čtvrtém případě voláme funkci i se jménem parametru. Toto je vhodné při větším množství parametrů – zlepšujeme čitelnost kódu.

Poznámka: Někteří z vás možná znají z jiných jazyků proměnnou s názvem args. PowerShell jí také disponuje, můžeme ji využít, ale při tvorbě vlastních funkcí bych se ve většině případů klaněl k pojmenování parametrů tak, jako v předchozím případě. Nicméně pouze pro názornost:

PS C:\> function Get-Args { "Pocet parametru: $($args.Count)"; "Parametry: $args" }
PS C:\> Get-Args aaa bbb ccc
Pocet parametru: 3
Parametry: aaa bbb ccc

Zajímavou možností je při definici parametrů funkce určit rovnou i jejich typ. Následující definice

PS C:\ function test { param ([int]$text) }

Nám při následujících typech volání buď projdou bez chyby nebo oznámí, že máme problém.

PS C:\> test 1
PS C:\> test aaa
test : Cannot process argument transformation on parameter 'text'. Cannot convert value "aaa" to type "System.Int32".
Error: "Input string was not in a correct format."
At line:1 char:5
+ test <<<< aaa
+ CategoryInfo : InvalidData: (:) [test], ParameterBindin...mationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,test

Vidíme, že PowerShell hlásí chybu konverze řetězce „aaa“ na číslo (typ int32). Zajímavým typem proměnné je přepínač (switch), který nám umožňuje rozhodování typu ano-ne. Vraťme se k naší první funkci Get-FreeDiskSpace, nyní přidáme možnost zobrazit množství volného místa v bytech (tak, jak jej vrátí cmdlet Get-WmiObject) nebo přepočtený na GB, jako v naší původní funkci.

function Get-FreeDiskSpace
{
param
  (
  $drive = 'C:',
  [switch]$gb
  )
  $filter = 'DeviceID="' + $drive + '"'
   if($gb)
   {
   (Get-WmiObject Win32_LogicalDisk -filter $filter).FreeSpace/1GB
   }
   else
   {
   (Get-WmiObject Win32_LogicalDisk -filter $filter).FreeSpace
  }
}

Parametr gb je typu [switch] a jeho uvedení při volání funkce ukáže volné místo v GB.

PS C:\> Get-FreeDiskSpace
211123945472
PS C:\> Get-FreeDiskSpace -gb
196.624496459961

Poznámka: Všimněte si, že při zadávání jednotlivých parametrů funkce můžete používat pro doplňování jmen klávesu “Tab” stejně jako při práci s cmdlety.

 

Pokročilé funkce

Pokud znáte funkce z PowerShellu v1, nebyly pro vás předchozí řádky vůbec žádnou novinkou. V PowerShellu v2 byl ovšem uveden concept takzvaných advanced functions (osobně nemám moc rád překlady zažitých anglických názvů do češtiny a nadpis této kapitoly je výjimkou).

Advanced functions přidávají několik klíčových slov a umožňují lepší kontrolu parametrů. Další přidanou hodnotou je možnost tvorby velice pěkné nápovědy (stejné jako mají cmdlety). Ukažme si, jak by mohla definice vstupního parametru vypadat.

function Show-ParameterAttribute
{
   param (
      [Parameter(
      Mandatory=$true,
      Position=0,
      ParameterSetName="set1",
      ValueFromPipeline=$false,
      ValueFromPipelineByPropertyName=$false,
      ValueFromRemainingArguments=$false,
      HelpMessage="Enter parameter #1.")]
      [Alias("p1")]
      [int]
      $param1
   )
  Write-Output $param1
}

Vidíme, že se nám možnosti pro definici parametrů pěkně rozrostly. Význam většiny atributů je zřejmě jasný z názvů, pojďme se nejdříve podívat, jak funguje naše funkce a poté si povíme o atributech podrobněji.

PS C:\> Show-ParameterAttribute
cmdlet Show-ParameterAttribute at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
param1: !?
Enter parameter #1.
param1: 0
0
PS C:\> Show-ParameterAttribute -param1 1
1
PS C:\> Show-ParameterAttribute -p1 2
2
PS C:\> Show-ParameterAttribute 3
3
PS C:\> Show-ParameterAttribute 4 5
Show-ParameterAttribute : A positional parameter cannot be found that accepts argument '5'.
At line:1 char:24
+ Show-ParameterAttribute <<<< 4 5
+ CategoryInfo : InvalidArgument: (:) [Show-ParameterAttribute], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Show-ParameterAttribute

Jelikož jsme parametr nastavili jako Mandatory, PowerShell nám při volání funkce bez parametrů oznámí, že musíme zadat hodnotu pro param1. Po vyvolání nápovědy pro parametr (označeno žlutě) se zobrazí text, který jsme zadali jako atribut HelpMessage. Při druhém volání jsme zadali parametr i se jménem a při třetím využili možnost námi definovaného aliasu. Ve čtvrtém případě zafungoval atribut Position. Poslední volání nám oznámí, že zadaná hodnota (5) nemůže být zpracována, protože nám „nezbyl“ parametr, kam bychom ji uložili. Pokud očekáváme, že na vstup se mohou dostat nějaké další parametry, které nechceme ztratit, můžeme změnit atribut ValueFromRemainingArguments na $true. Pokračujeme definicí nového parametru.

[Parameter(ValueFromRemainingArguments=$true)]$param2
PS C:\> Show-ParameterAttribute 4 5 6 7
param1: 4, param2: 5 6 7

Vidíme, že parametr param1 obsahuje první hodnotu a zbývající tři jsou obsaženy v param2 (v těle funkce jsme změnili výpis na: Write-Output "param1: $param1, param2: $param2").

Nejenom, že můžeme ovlivňovat chování parametrů, můžeme je rovnou na vstupu funkce testovat na určité hodnoty. Tím nám v tělě funkce odpadnou podmínky typu if-else a kód bude tím pádem čitelnější. Můžeme použít následující atributy (takzvané Parametr Validation Attributes).

  • AllowNull – umožňuje, aby parametr obsahoval hodnotu null. Platí i pokud je parametr nastaven jako mandatory.
  • AllowEmptyString – parametr může být prázdný řetězec.
  • AllowEmptyCollection – stejné jako předchozí dva, ale pro prázdnou kolekci.
  • ValidateCount – určuje minimální a maximální počet argumentů.
  • ValidateLength – určuje délku parametru.
  • ValidatePattern – specifikuje regulární výraz, kterým můžeme určit vzor parametru.
  • ValidateRange – minimální a maximální hodnota parametru.
  • ValidateScript – může obsahovat skript, který validuje parametr. Pokud skript vrátí hodnotu false, PowerShell zahlásí chybu validace.
  • ValidateSet – parametr může obsahovat pouze uvedené hodnoty.
  • ValidateNotNull – hodnota parametru neůže být null.
  • ValidateNotNullOrEmpty – hodnota nemůže být null nebo prázdný řetězec.

Ukažme si krátký příklad.

function Test-Parameter
{
   param (
   [Parameter(Mandatory=$True,Position=0)]
   [string]
   [ValidateSet("David")]
   $jmeno,
   [Parameter(Mandatory=$true,Position=1)]
   [int]
   [ValidateRange(33,33)]
   $vek
   )
   Write-Output "Autorem je $jmeno a je mu $vek let."
}

Tato funkce vypíše, kdo je autorem tohoto článku, ovšem pouze za podmínky, že zadáme správně jméno a věk.

PS C:\> Test-Parameter Petr 33

Test-Parameter : Cannot validate argument on parameter 'jmeno'. The argument "Petr" does not belong to the set "David" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
At line:1 char:15
+ Test-Parameter <<<< Petr 33
+ CategoryInfo : InvalidData: (:) [Test-Parameter], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Parameter

PS C:\> Test-Parameter David 32

Test-Parameter : Cannot validate argument on parameter 'vek'. The 32 argument is less than the minimum allowed range of 33. Supply an argument that is greater than 33 and then try the command again.
At line:1 char:15
+ Test-Parameter <<<< David 32
+ CategoryInfo : InvalidData: (:) [Test-Parameter], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Test-Parameter

PS C:\Scripts> Test-Parameter David 33

Autorem je David a je mu 33 let.

Vidíme, že jsme zkusili zadat špatně buď věk nebo jméno a v obou případech jsme obdrželi chybové hlášení. Až v posledním případě proběhlo vše podle předpokladů.


Tvorba nápovědy

Posledním dnešním tématem bude vytvoření nápovědy pro naši vlastní funkci. Asi jako většina lidí se mohu přiznat k tomu, že jsem líný psát dokumentaci k vlastním skriptům. I když netvrdím, že ve verzi 2 se za vás dokumentace napíše sama, alespoň nám PowerShell tým trošku ulehčil práci. V každém případě jde ale především o naši vlastní vůli zadat těch pár klíčových slov navíc.

Určitě už jste použili v kódu znak # - cokoli od tohoto znaku do konce řádky je bráno jako komentář. Ve verzi 2 přibyla možnost použít znaky <# a #> a cokoli mezi těmito znaky je bráno jako komentář, tento komentář může zasahovat přes více řádek. Této vlastnosti lze využít právě pro vytvoření vlastní nápovědy – jedinou podmínkou je, že použijeme speciální klíčová slova.

Pojďme si vytvořit help pro první funkci z tohoto článku.

function Get-FreeDiskSpace
{

<#
.Synopsis

Funkce zjišťuje volné místo na disku.

.Description
Pomocí WMI třídy Win32_LogicalDrive zjistí volné místo na zadaném disku.

.Parameter drive
Určuje disk, pro který zjišťujeme volné místo.

.Example
Get-FreeDiskSpace
129.7597

.Example
Get-FreeDiskSpace -drive D:
10.86

.Link
Get-WmiObject
#>

param
  (
    $drive = 'C:'
  )

$filter = 'DeviceID="' + $drive + '"'
(Get-WmiObject Win32_LogicalDisk -filter $filter).FreeSpace/1GB

}

Při volání nápovědy se zobrazí následující text.

PS C:\> Get-Help Get-FreeDiskSpace -Full

NAME
Get-FreeDiskSpace

SYNOPSIS
Funkce zjišťuje volné místo na disku.

SYNTAX
Get-FreeDiskSpace [[-drive] <Object>] [<CommonParameters>]

DESCRIPTION
Pomocí WMI třídy Win32_LogicalDrive zjistí volné místo na zadaném disku.

PARAMETERS
-drive <Object>

Určuje disk, pro který zjišťujeme volné místo.
Required?                                          false
Position?                                           1
Default value
Accept pipeline input?                     false
Accept wildcard characters?

<CommonParameters>
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer and OutVariable. For more information, type,
"get-help about_commonparameters".

INPUTS

OUTPUTS

-------------------------- EXAMPLE 1 --------------------------

C:\PS>Get-FreeDiskSpace
129.7597

-------------------------- EXAMPLE 2 --------------------------

C:\PS>Get-FreeDiskSpace -drive D:
10.86

RELATED LINKS
Get-WmiObject

Vidíme, že bez zbytečně velké námahy máme vygenerovanou velice pěknou nápovědu.

Tím bychom pro dnešek skončili. Pokud vás tvorba funkcí zaujala, zkuste se podívat na následující témata nápovědy:

  • about_functions_advanced
  • about_functions_advanced_methods
  • about_functions_advanced_parameters
  • about_Comment_Based_Help

- David Moravec