PowerShell: funkcja na podstawie skryptu

Nadchodzi taki czas w pracy administratora gdy jego skrypty są tak często używane że mogłyby stać się „własnym natywnym” poleceniem PowerShella – czyli funkcją. Z fedinicji jest ona blokiem kodu (skryptem), który może być wielokrotnie wywoływany w powłoce. Ale jak utworzyć funkcje PowerShell? Jak dodać funkcje do profilu PowerShell. W jaki sposób przerobić skrypt na funkcje?

Za każdym razem gdy pisze nowy skrypt i chce go przekształcić w funkcje próbuje sobie przypomnieć dokładny schemat wewnątrz bloku parametrów. Poprawnie zbudowana funkcja powinna mieć wszystkie ważne elementy składowe. Osobiście ułatwiam sobie prace korzystając z raz wypracowanego przez lata szablonu, dzięki czemu zamiana skryptu na funkcje staje się dużo prostsza.

Pełną dokumentacje znajdziecie oczywiście na stronie Microsoftu, poniżej znajdziecie sprawdzony szablon dla tworzenia funkcji PowerShell oraz krótki przykład jak skrypt zamienić na funkcje.

W naszym przykładzie mamy skrypt PowerShell który z wczytanej listy maszyn, wyciąga informacje o czasie jej startu (bootowania) oraz o ostatnim wpisie w dzienniku zdarzeń który może dotyczyć przyczyny takiego zdarzenia
.
W skrypcie mamy dwa polecenie, pierwsze używa Get-ComputerInfo i pobiera z niego obiekt OsLastBootUpTime

(Get-ComputerInfo).OsLastBootUpTime

drugie to Get-WinEvent które przejrzy dziennik zdarzeń SYSTEM w poszukiwaniu ostatniego zdarzenia o określonym ID (z kilku które mówią nam coś o restarcie/uruchomieniu maszyny) i pobierze treść komunikatu z wpisu:

(Get-WinEvent -FilterHashtable @{logname = 'System'; id =  1074,6005,6006,6008} -MaxEvents 1).message

Oba polecenia zostały ubierane w skrypt który zrobi to zdalnie z użyciem polecenia Invoke-Command na liście maszyn, listę wczytamy z pliku tekstowego:

#get computer list from file
$computers = Get-Content "C:\data\list.txt"


foreach($computer in $computers) 
    {
    #check if the computer is available
    if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) 
           {
           #if it is available, execute the script on it
            Invoke-Command -ComputerName $computer -ScriptBlock { 
                                #get hostname; get last boot time; get message from eventlog       
                                $boot=(Get-ComputerInfo).OsLastBootUpTime; 
                                $why=(Get-WinEvent -FilterHashtable @{logname = 'System'; id = 1074,6005,6006,6008} -MaxEvents 1).message
                                Write-Host "$env:ComputerName has been booted on: $boot with event log message: $why"
                                }
           }
    #write message if computer is not available
    else { 
            Write-Host "Caution: $computer is not available!"
            }
    }

Skrypt działa i sprawdza się doskonale, natomiast jako administrator uruchamiam go dość często i chciałbym to robić z poziomu konsoli zaraz po zalogowaniu, nie pamiętając jego ścieżki, nie zmieniając jego zawartości chociażby listy komputerów ale używając w zamian parametru….tutaj przychodzi nam z pomocą funkcja PowerShell.

Zacznijmy od jej pustego szablonu którego używam za każdym razem przy tego typu pracach, wygląda on następująco:

function Get-LastBootInfo {

 <#
.SYNOPSIS
    Basic function description


.NOTES
    Name: Example template
    Author: sprawdzone.it
    Version: 1.0
    DateCreated: 2015-09-07


.EXAMPLE
    How to use this function 


.LINK
    https://github.com/sprawdzoneit
#>

#verbose etc.
[cmdletbinding()] 

#function parameter block
param (
    [Parameter(
    ValueFromPipeline = $true, 
    ValueFromPipelineByPropertyName = $true,            
    Mandatory = $true,     
    Position=0,
    HelpMessage = 'Computer name')]
    [string[]]$Computer #double [[]] accepts multiple string value - array
    )
    

#rune once per function
begin{ 
    Write-Verbose "Program started"
    $out = @()
}

#main features of the function
process{
        Write-Verbose "Program runs"
        Write-Host "Getting information from: $Computer"
       $out =
        

#end run once per function
end{
  $out
  Write-Verbose "Function ends here"
  
}

}


}

Posiada on kilka stałych elementów które modyfikujemy w zależności od potrzeb:

  • blok opisu, notes, przykłady etc.
  • blok parametrów (nasz przykład ma jeden zdefiniowany parametr $Computer
  • blok początku (uruchamiany raz, przed główną częscią funkcji)
  • blok procesu (główna cześć, to tutaj wykonujemy elementy naszej funkcji)
  • blok końca (również uruchamiany jednorazowo, zazwyczaj tutaj prezentuje wynik naszej funkcji)

Jak zauważycie elementy szablonu mają dość czytelne nazwy a w moim przkładzie opisane są również komentarzem.

Mając szablon zabieramy się do pracy, kopiujemy jego dowego okna naszego ulubionego edytora PowerShell np Visual Studio Code.

Edytujemy: opis, zakomentujmy na chwile parametr $out (blok początku, blok procesu, blok końca). W bloku procesu wklejmy nasz skrypt ale na razie a dokładnie tylko taką jego część:

#check if the computer is available
        if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) 
        {
        #if it is available, execute the script on it
        Invoke-Command -ComputerName $computer -ScriptBlock { 
                            #get hostname; get last boot time; get message from eventlog       
                            $boot=(Get-ComputerInfo).OsLastBootUpTime; 
                            $why=(Get-WinEvent -FilterHashtable @{logname = 'System'; id = 1074,6005,6006,6008} -MaxEvents 1).message
                            Write-Host "$env:ComputerName has been booted on: $boot with event log message: $why"
                            }
        }
        #write message if computer is not available
        else { 
            Write-Host "Caution: $computer is not available!"
            }
        }

Całość powinna wyglądać następująco:

function Get-LastBootInfo {

    <#
   .SYNOPSIS
       The function checks when the OS was started, collecting information about the reason for booting from the event log
   
   .NOTES
       Name: Get-LastBootInfo
       Author: sprawdzone.it
       Version: 1.1
       DateCreated: 2015-09-07
   
   .EXAMPLE
       Get-LastBootInfo -computer ad01.sprawdzone.it
   
   .LINK
       https://github.com/sprawdzoneit/powershell-functions
   #>

#verbose etc.
[cmdletbinding()] 

#function parameter block
param (
    [Parameter(
    ValueFromPipeline = $true, 
    ValueFromPipelineByPropertyName = $true,            
    Mandatory = $true,     
    Position=0,
    HelpMessage = 'Computer name')]
    [string[]]$Computer #double [[]] accepts multiple string value - array
    )
    

#rune once per function
begin{ 
    Write-Verbose "Program started"
    #$out = @()
}

#main features of the function
process{
        Write-Verbose "Program runs"
        Write-Host "Getting information from: $Computer"
        #$out =
        #check if the computer is available
        if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) 
        {
        #if it is available, execute the script on it
        Invoke-Command -ComputerName $computer -ScriptBlock { 
                            #get hostname; get last boot time; get message from eventlog       
                            $boot=(Get-ComputerInfo).OsLastBootUpTime; 
                            $why=(Get-WinEvent -FilterHashtable @{logname = 'System'; id = 1074,6005,6006,6008} -MaxEvents 1).message
                            Write-Host "$env:ComputerName has been booted on: $boot with event log message: $why"
                            }
        }
        #write message if computer is not available
        else { 
            Write-Host "Caution: $computer is not available!"
            }
        }
 
#end run once per function
end{
    #$out
    Write-Verbose "Function ends here"
    
  }        


}

Sprawdźmy teraz jak działa nasza funkcja:

Get-LastBootInfo -Computer hostname

Funkcja działa, można wprowadzać nawet kilka nazw hostów jednocześnie czy wskazywać do niej parametr -Computer z innej zmiennej np $computers (tak jak robiliśmy to wcześniej w skrypcie z użyciem pliku)

Chcielibyśmy jednak iść o krok dalej i wskazać do naszej funkcji dane z pipe z innego polecenia czyli chociażby dla przykładu:

$env:ComputerName | Get-LastBootInfo

Aby taka forma funkcji zadziałała dokonajmy drobnych poprawek. Odkomentujmy paramety $out (blok startu, procesu i końca) a nasz skrypt w bloku procesu zamknijmy w pętli foreach przypisując każdy wynik do $out używając +=

function Get-LastBootInfo {

 <#
.SYNOPSIS
    The function checks when the OS was started, collecting information about the reason for booting from the event log


.NOTES
    Name: Get-LastBootInfo
    Author: sprawdzone.it
    Version: 1.1
    DateCreated: 2015-09-07


.EXAMPLE
    Get-LastBootInfo -computer ad01.sprawdzone.it
    Get-LastBootInfo -computer ad01, ad02
    $computers = get-content c:\computers.csv; Get-LastBootInfo -computer $computers


.LINK
    https://github.com/sprawdzoneit/powershell-functions
#>

[cmdletbinding()] #verbose etc.

param (
    [Parameter(
    ValueFromPipeline = $true, 
    ValueFromPipelineByPropertyName = $true,            
    Mandatory = $true,     
    Position=0,
    HelpMessage = 'Computer name')]
    [string[]]$computers #double [[]] accepts multiple string value - array
    )
    

#rune once per function
begin{ 
    Write-Verbose "Program started"
    $out = @()
}


process{
        Write-Verbose "Program runs"
        Write-Host "Getting information..." 
       $out += foreach($computer in $computers) 
                                        {
                                        if (Test-Connection -ComputerName $Computer -Count 1 -ErrorAction SilentlyContinue) {
                                                                                            Invoke-Command -ComputerName $Computer -ScriptBlock {

                                                                                                #get last boot time
                                                                                                $boot = (Get-ComputerInfo).OsLastBootUpTime

                                                                                                #get event
                                                                                                <#
                                                                                                Description:

                                                                                                1074 System has been shutdown by a process/user

                                                                                                6005 The Event log service was started

                                                                                                6006 The Event log service was stopped

                                                                                                6008 The previous system shutdown at time on date was unexpected

                                                                                                6013 The system uptime is number seconds

                                                                                                Event ID 1704 documents shut down events. 
                                                                                                Event ID’s 6006, 6008 and 6013 document events related to a power cycle and may or may not be useful depending on your particular situation. 
                                                                                                Pairing the 6000 events with 1074 gives a picture of how long restart operations took to complete. 
                                                                                                6008 is important for recognizing when a computer may have blue screened or lost power unexpectedly. 
                                                                                                6013 is not related to power cycle events, instead, it documents how long a computer has been running since the last restart. 
                                                                                                #>
                                                                                                $why = (Get-WinEvent -FilterHashtable @{logname = 'System'; id = 1074,6005,6006,6008} -MaxEvents 1).Message

                                                                                                Write-Host "For $ComputerName last boot time is $boot with last log entry: $why"
                                                                                                                                                                                                
                                                                                                                                                }
                                                                                                                                        }
                                                    else { 
                                                        Write-Host "Caution: $computer is not available!"
                                                        }
                                        }
    }

#end run once per function
end{
  $out
  Write-Verbose "Function ends here"
  
}


}

Mamy działającą funkcje teraz czas abyśmy dodali ją do profilu PowerShell np profilu użytkownika.
Profil użytkownika PowerShell to skrypt, który zostaje automatycznie uruchamiany przy każdym uruchomieniu konsoli PowerShell przez tego użytkownika.
Możesz umieścić w nim niestandardowe funkcje, aliasy, zmienne i inne konfiguracje konsoli, które chcesz mieć dostępne za każdym razem, gdy używasz PowerShell (również gdy uruchamiasz ją jako administrator).

Sprawdźmy najpierw naszą zmienną $profile, zobaczymy gdzie znajduje się nasz plik profilu, następnie otwórzmy go np w notatniku i w klejmy tam naszą funkcje.
Zapisujemy plik i uruchamiamy PowerShell’a ponownie – funkcja będzie już zaaplikowana do bieżącej sesji – poniżej zrzut ekranu z takiego procesu

Gotową funkcje nad którą pracowaliśmy w powyższym przykładzie znajdziecie w naszym repozytorium na Github.


Jeżeli mój wpis Ci się spodobał, pomógł w pracy? Chcesz mnie wspierać? Postaw kawę! To dzięki waszemu wsparciu nie ma reklam! Poniżej kod QR do płatności który jest jednocześnie linkiem do PayPal