Posted in: Základy PowerShellu

Funkce v PowerShellu

Dnes po dlouhé době opět píši něco pro ty, kteří chtějí s PowerShellem začínat a již nějak zvládli základní syntaxi. Pro dnešní práci budeme potřebovat nějaký nástroj k psaní skriptů, viz článek.

Jak jsem se již určitě zmínil v některém předešlém článku, skript je posloupnost příkazů, které se vykonají v daném pořadí. Skript může obsahovat, stejně jako program v některém programovacím jazyce, cykly, podmínky a další řídící příkazy, které vedou na opakování nějakého bloku, nebo naopak přeskočení nějakého bloku kódu. Stejně jako v programování, i při psaní skriptů, můžeme potřebovat opakovat nějaký blok, kus kódu, na různých místech skriptu. Prvním nápadem může být, prostě daný blok kódu zkopírovat a vložit jej na požadovaná místa. Toto je nevýhodné, protože se zkopírovaný blok špatně opravuje, rozšiřuje apod. Proto máme lepší řešení, to jsou funkce, které si umíme volat.

Jmenná konvence

Když si vzpomenete na základní syntaxi PowerShellu, příkazy mají základní podobu:
sloveso-PodstatnéJméno
Tento formát pojmenování je potřeba dodržet i u našich funkcí. Mnohé z vás by mohlo napadnou použít vlastní kombinaci jako například:
Vypis-PCinfo
sice syntakticky vyhovíme, ale není to zcela správné. PowerShell definuje rovněž sadu sloves, která máme využívat a uživatelé jazyka je očekávají a očekávají, že budou mít daný význam. Seznam podporovaných sloves vrátí příkaz:

Get-Verb | Sort-Object -Property Verb

Výstup této funkce rovněž uvádím v článku o syntaxi.

Obecná syntaxe

function Sloveso-PodstatneJmeno
{
    #vlastní kód (tělo) funkce
}

Příklad:

function Get-WinInfo
{
    cls
    Write-Host "Základní informace o počítači"
    Write-Host ""
    $info = Get-ComputerInfo | select WindowsProductName, WindowsCurrentVersion, CsModel, CsName
    Write-Host "Název OS      :" $info.WindowsProductName
    Write-Host "Verze jádra OS:" $info.WindowsCurrentVersion
    Write-Host "Model zařízení:" $info.CsModel
    Write-Host "Název zařízení:" $info.CsName
}

Funkci spustíme tak, že napíšeme její jméno, tedy pro náš příklad by volání vypadalo:

Get-WinInfo

Výše uvedená je nejsprostější verze, samozřejmě i základní funkce může mít parametry, případně vracet hodnoty.

Funkce s parametrem

Výše popsaný základ je dobrý, ale pořád se jedná o poměrně hloupé funkce. Výše popsaná syntaxe slouží k deduplikaci kódu a umí modifikovat globální proměnné, vypisovat na obrazovku, komunikovat s uživatelem atd. Bohužel nemůžeme takto definované funkci předat hodnoty ke zpracovaní. Pojďme se podívat na to, jak tento problém řešit.

Obecná syntaxe:

function Sloveso-PodstatneJmeno
{
     param(
       $NázevParametru,
       $NázevDruhéhoVolitelnéhoParametru
       )
     #vlastní kód (tělo) funkce
}

Čárkou se oddělují jednotlivé parametry, tudíž se uvádí pouze tam, kde za parametrem následuje další parametr. Pojďme si uvést příklad funkce s jedním parametrem.

function Get-OSInfo { 
    param (
        $ComputerName
    ) 
    Get-WmiObject -ComputerName $ComputerName -Class Win32_OperatingSystem | select Caption, Version, SystemDrive | Format-Table 
}

Nyní je na čase si ukázat, jak tuto funkci volat. Obecně voláme funkci v podobě:

Sloveso-PodstatneJmeno -NázevParametru hodnota -NázevDruhéhoVolitelnéhoParametru hodnota

Pojďme si to zkonkrétnit na našem příkladu s funkcí Get-OSInfo pro lokální počítač:

Get-OSInfo -ComputerName localhost

Toto vše je krásné, ale občas bychom potřebovali, aby vstup, který nám dává parametr, byl určitého datového typu, například číslo. Pojďme si na příkladu jednoduché kalkulační funkce ukázat, jak určit datový typ, který parametr bude akceptovat.

function Get-Vysledek { 
    param (
        [double]$cislo1,
        [double]$cislo2,
        [switch]$soucet,
        [switch]$soucin,
        [switch]$rozdil,
        [switch]$podil
    ) 
    if($soucet)
    {
        $vysledek = $cislo1 + $cislo2
        Write-Host "Soucet je:" $vysledek
    }
    if($rozdil)
    {
        $vysledek = $cislo1 - $cislo2
        Write-Host "Soucet je:" $vysledek
    }
    if($soucin)
    {
        $vysledek = $cislo1 * $cislo2
        Write-Host "Soucet je:" $vysledek
    }
    if($podil)
    {
        if($cislo2 -gt 0)
        {
            $vysledek = $cislo1 / $cislo2
            Write-Host "Soucet je:" $vysledek
        }
        else
        {
            Write-Host "Nulou nelze dělit"
        }
    }
}

Datový typ, kterého musí daný parametr nabývat, určíme tak, že název datového typu uvedeme do hranatých závorek před požadovaný parametr. Jedinou výjimkou, která funguje pouze u parametrů, nelze ji uplatnit na proměnné v jiných částí kódu je „datový typ“ switch. Pokud je nějaký parametr typu switch, pak daný parametr nabyde hodnoty True (logické jedničky) tím, že dojde k uvedení daného parametru při volání procedury. Úplné informace o datových typech jsou v dokumentaci. Někdy příště, se na vybrané datové typy podíváme. Příklad volání naší ukázkové funkce Get-Vysledek:

Get-Vysledek -cislo1 6.6 -cislo2 3 -podil

Návratová hodnota

Od PowerShell verze 5 nám funkce může vrátit návratovou hodnotu, stejně jako je tomu v běžných programovacích jazycích. Na rozdíl do programovacích jazyků ovšem PowerShell nevrací hodnotu, ale výraz. Na rozdíl od programovacích jazyků u funkce nedefinujeme datový typ návratové hodnoty. Stejně jako v případě běžných programovacích jazyků máme k dispozici příkaz return, jehož syntax je jednoduchá:

return vyraz

Pojďme si to ukázat na příkladu převzatém z dokumentace a lehce upraveném.

function Get-Kalkuace {
    param ($hodnota)

    $hodnota ="Prosím vyčkejte, počítám...`n" #znak `n je zalomení řádku
    $hodnota += 73
    return $hodnota
}

Takovouto funkci lze volat napřímo, nebo její výstup, který vrací příkaz return přiřadit do proměnné. Pojďme si ukázat verzi, kdy hodnotu navrácenou funkcí (v mé modifikaci strinng) přiřadíme do proměnné, kterou rovnou vypíšeme:

Write-Host ($a = Get-Kalkuace 14)

Závěr

Toto je vše z dnešního úvodu do funkcí. Funkce toho umí dalece více, ale o tom až někdy později u tzv. pokročilých funkcí. Funkce jsou klíčovou součástí jazyka PowerShell, protože moduly jazyka PowerShell vždy funkce obsahují a uživatel modulu volá právě funkce v něm obsažené.

Posted in: Windows 10, Windows 11, Windows 8 a 8.1, Windows server, Základy PowerShellu

Nástroje pro skriptování v PowerShellu

PowerShell, stejně jako další textové Shelly má 2 tváře, příkazové rozhraní a skriptování. Pro příkazové rozhraní slouží konzola PowerShell (na Windows PowerShell.exe), nebo Windows Terminal. Příkazové prostředí slouží k manuálnímu provádění příkazů, nebo spouštění skriptů. Skriptovací rozhraní je malinko složitější, takže si jej rozebereme dále trochu podrobněji.

Nativní nástroj na Windows – PowerShell ISE

Všechny verze Windows s grafickým rozhraním (tedy všechny krom Windows server Core a Windows server Nano) obsahují nástroje PowerShell ISE, který slouží k editaci a tvorbě skriptů a dalších PowerShell souborů. PowerShell ISE je primárně určeno pro Windows PowerShell (tedy PowerShell verze 1.0 až 5.1), pro PowerShell verze 6 a novější je sice možné ISE využít, ale doporučeno je využití VS Code, kde je nápověda a doplňování příkazů nastavená primárně na nejnovější podporovanou verzi PowerShellu. Z nástrojů ostatních vývojářů je snad nejpokročilejším PowerShell studio, ale osobně i tak dávám přednost jednoduchému ISE.

Krom interaktivní možnosti vyhledávání příkazů, jejichž parametry je možné vyplnit pomocí formuláře, umožňuje využívat a vytvářet tzv. snipety.

zadání příkazů z vyhledání a vyplnění formuláře po pravé straně

Snipety

Snipety jsou šablony většího kusu syntaxe, jako jsou třídy, DSC, funkce, cykly či podmínky. Seznam dostupných snipetů se vyvolá stiskem CTRL + J v místě, kam chceme daný snipet vložit.

Ukázka seznamu snipetů obsaženém v CTRL + J

Pojďme si nyní ukázat, jak využít snipet pro tvorbu tzv. andvanced function. Nejprve pomocí CTRL + J vyvoláme nabídku snipetů a v ní najdeme Advanced Function. Po kliknutí na tuto nabídku se nám vloží kód.

Jak je vidět na obrázku výše, vloží se nám šablona kódu, kterou začneme doplňovat, a výsledek může být např.:

<#
.Synopsis
   kontrola systémovýh souborů.
.DESCRIPTION
   kontrola systémových souborů nativními nástroji scf a dism
.EXAMPLE
   Check-SystemFiels -scannow
   provede příkaz sfc /scannow
.EXAMPLE
   Check-SystemFiels -scanfile -Path c:\windows\system32\kernel32.dll
   Provede kontrolu integrity a pokusí se o opravu souboru kernel32.dll
#>
function Check-SystemFiles
{
    Param
    (
        # nápověda
        [Parameter(ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [string] $path,

        # scannow
        [switch] $scannow,

        #verifikace
        [switch] $verifyonly,

        #kontrola souboru
        [switch] $scanfile,

        #offline kontrola
        [switch] $offline,

        #kontrola souboru
        [switch] $verifyfile,

        #offline boot adresář
        [string] $offbootdir,

        #offline winadresář
        [string] $offwindir,

        #oprava MS Store komponent
        [switch] $store
    )

    Begin
    {
        if((whoami /priv /fo csv | convertfrom-csv | select "Privilege Name")."Privilege Name" -contains "SeImpersonatePrivilege")
        {}
        else
        {
            Write-Error "Tento úkon smí vykonávat pouze správce"
            Start-Process "$PSHOME\powershell.exe" -Verb Runas -ArgumentList "-command 'import-module AdminTools'"
        }
        if($scanfile -and ($path -eq ''))
        {
            Write-Error 'Není uvedená cesta k ověřovanému souboru'
            Pause
        }elseif($verifyfile -and ($path -eq ''))
        {
            Write-Error 'Není uvedená cesta k ověřovanému souboru'
            Pause
        }elseif($offline -and (($path -eq '') -or ($offbootdir -eq '') -or ($offwindir -eq '')))
        {
            Write-Error 'Není uvedená cesta k některému z offline adresářů'
            Pause  
        }
    }
    Process
    {
        if($scannow)
        {
            $prikaz = 'sfc /scannow'
            Invoke-Expression -Command $prikaz
        }
        elseif ($verifyonly)
        {
           $prikaz = 'sfc /verifyonly' 
           Invoke-Expression -Command $prikaz
        }
        elseif ($scanfile)
        {
            $prikaz = 'sfc /scanfile=' + $path
            Invoke-Expression -Command $prikaz
        }
        elseif($verifyfile)
        {
            $prikaz = 'sfc /VERIFYFILE=' + $path
            Invoke-Expression -Command $prikaz
        }
        elseif($offline)
        {
            $prikaz = 'sfc /scanfile=' + $path + '/OFFBOOTDIR=' + $offbootdir + '/OFFWINDIR=' + $offwindir
            Invoke-Expression -Command $prikaz
        }elseif($store)
        {
            $prikaz = 'DISM /Online /cleanup-image /ScanHealth'
            Invoke-Expression -Command $prikaz
            $prikaz = 'dism /online /cleanup-image /restorehealth'
            Invoke-Expression -Command $prikaz
        }
    }
    End
    {
    }
}

Na kódu výše je vidět, že některé části šablony jsem nevyužil a tudíž jsem je smazal. Výhodou využití snipetu je, že člověk při psaní nezapomene na žádnou část včetně nápovědy, která je opravdu důležitá.

Tvorba vlastního snipetu

PowerShell ISE není omezeno pouze na výchozí sadu snipetů, ale každý člověk si může vytvořit vlastní snipety. Jde o dokumenty ve formátu ps1xml. K vytvoření slouží příkaz New-IseSnippet a vlastní šablona (tělo snpetu) se vkládá jako text. Dále uvádím 2 příklady, kterými jsem si rozšířil své ISE, první generuje blok nápovědy k funkci (protože si často píši tělo funkce ručně bez snipetu)

New-IseSnippet -Title Comment-BasedHelp -Description "A template for comment-based help." -Text "<#
.SYNOPSIS
.DESCRIPTION
.PARAMETER  <Parameter-Name>
.INPUTS
.OUTPUTS
.EXAMPLE
.LINK
.NOTE
#>" -Force

Možnost Force říká, že má dojít k přepisu již existujícího snipetu se stejným jménem, tímto parametrem se dosahuje aktualizací (oprav) vlastních snipetů. Druhý příklad prak tvoří šablonu pro vložená povinného parametru funkce či skriptu.

$M = @'
Param
(
  [parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,Position=<pozice>)]
  [<type>]
  $<ParameterName>
)
'@
New-ISESnippet -Text $M -Title MandatoryParameter -Description "Adds a mandatory function parameter." -Force

Import Snipetů

Snipety je možné sdílet s kolegy, nebo mezi více zařízeními pomocí sdílené složky, nebe vyměnitelného média. Bohužl takto dostupné snipety není možné hned využívat, ale musí dojít k jejich importu, který se děje pomocí příkazu Import-IseSnippet viz příkaz níže:

Import-IseSnippet -Path \\Server01\Public\Snippets -Recurse

Alternativou je snipety importovat pomocí správy souborů. (takto nelze snipety importovat ze snipetových modulů). Ze zdíleného média zkopírujeme požadované snipety (soubory formátu ps1xml) do: %userprofile%\Documents\WindowsPowerShell\Snippets
Takto přidané snipety jsou k dispozici až po novém spuštění ISE.

Podrobnější informace o snipetech najdete v dokumentaci Na závěr prozradím, že snipety není možné využívat ve VS Code a PowerShell studio má vlastní sadu obdobných možností.

Back to Top