Naplófájl létrehozása, régi fájlok törlése (New-LogFile) és a naplózás indítása (Initialize-Logging)

Ahogy a fejezet bevezetőjében is írtam, a naplózáshoz szükségünk van egy fájlra, amibe majd a naplóbejegyzéseket írjuk, ez a New-LogFile függvény. Ez a függvény fogja törölni a régi fájlokat, és mivel ez a fajta funkcionalitás egyéb fájlokra is lehet használni, így ezt majd exportáljuk a modulból:

function New-LogFile {

param(

    [string] $name,

    [string] $path = $global:logging.LogFolder,

    [int]    $keepdays = 60,

    [switch] $byseconds,

    [switch] $overwrite

)

   

    if(!(Test-Path -Path $path -PathType Container)){

        [void] (New-Item -Path $path -ItemType Directory -ErrorAction Stop)

    }

   

    if($byseconds){

        $datepart = Get-Date -Format 'yyyyMMddHHmmss'

    }

    else{

        $datepart = Get-Date -Format 'yyyyMMdd'

    }

 

    $filename = $name -replace "(?=\.(?!.*?\.))", "-$datepart"

    $searchname = $name -replace "(?=\.(?!.*?\.))", "-*"

 

    if($keepdays){

        Get-ChildItem -Path $path -Filter $searchname | Where-Object {((get-date) - $_.CreationTime).totaldays -gt $keepdays} |

            ForEach-Object {

                New-LogEntry "Removing obsolete file: '$($_.FullName)'" -indent 1

                Remove-Item -Path $_.FullName

            }

    }

 

    if($overwrite -or (!(Test-Path -Path (Join-Path -Path $path -ChildPath $filename)))){

        New-Item -Path $path -Name $filename -ItemType file -Force:$overwrite | Add-Member -MemberType NoteProperty -Name New -Value $true -PassThru

    }

    else{

        Get-Item -Path (Join-Path -Path $path -ChildPath $filename) | Add-Member -MemberType NoteProperty -Name New -Value $false -PassThru

    }

}

A függvényben elsőként létrehozzuk a fájl könyvtárát, ha esetleg az még nem létezne. Majd a fájl tényleges nevének dátum részét képezzük: alapban csak az aktuális dátumból, de ha a $byseconds kapcsolót használtuk, akkor még az idő számjegyeit is belerakjuk.

Ha van 0-tól különböző $keepday érték megadva, akkor ugyanabban a könyvtárban megkeressük a $searchname alapján azokat a fájlokat, amelyek „fájlnév-*.kiterjesztés” sémára illeszkednek és idősebbek, mint $keepdays nap és azokat töröljük. A törlés tényét naplózzuk is majd.

Ha még nem létezik a fájl, vagy az $overwrite kapcsolót használtuk, akkor újként hozzuk azt létre, egyébként csak megragadjuk a fájlt. A függvényünk visszatérési értéke az így generálódó „item” objektum lesz, kiegészítve a New tulajdonsággal, ami $true, ha új a fájl, és $false, ha már egy meglevőt használunk. Ezt majd arra használjuk fel a naplófájlok írásánál, hogy kell-e fejlécet írnunk bele vagy sem.

Jöjjön a naplófájl létrehozásának függvénye:

function Initialize-Logging {

[CmdletBinding()]

param(

    [string] $title,

    [string] $path,

    [int]    $keepdays = 60,

    [int]    $progressbarsec = 1,

    [int]    $progresslogfirst = 60,

    [int]    $progresslogmin   = 5

)

    $scriptinvocation = (Get-PSCallStack)[1].InvocationInfo

 

    (Get-Variable -Name Error -Scope global -ValueOnly).Clear()

 

    if(!$path){

        if($scriptinvocation.MyCommand.path){

            $path = Split-Path $scriptinvocation.MyCommand.path

        }

        else{

            $path = $env:TEMP

        }

 

        $path = Join-Path $path Logs

    }

 

    $version = "0.0.0"

    $releasedate = ""

 

    if(Get-Member -InputObject $scriptinvocation.MyCommand -Name scriptcontents -ErrorAction Ignore){

        $scripttext = $scriptinvocation.MyCommand.scriptcontents

        $versionfound = $scripttext -match "Version\s*:\s*(?<version>\d+\.\d+(.\d+)*)(\s*\((?<releasedate>\d{4}\.\d{2}\.\d{2})\))?"

        if($versionfound){

            $releasedate = $Matches.releasedate

            $version = $Matches.version

        }

    }

   

    if($scriptinvocation.MyCommand.Name){

        $scriptname = $scriptinvocation.MyCommand.Name

    }

    else{

        $scriptname = "Interactive"

    }

 

    if($scriptinvocation.MyCommand.path){

        $scriptpath = Split-Path -Path $scriptinvocation.MyCommand.path

    }

    else{

        $scriptpath = "Interactive"

    }

 

    $logFile = New-LogFile -name "$($scriptname).log" -path $path -keepdays $keepdays

 

    $global:logging = [pscustomobject]@{

                Title = $title

                ScriptName = $scriptname

                ScriptPath = $scriptpath

                ScriptVersion = $version

                RunBy = "$env:USERDOMAIN\$env:USERNAME"

                Computer = $env:COMPUTERNAME

                LogPath = $logFile.fullname

                LogFolder = $logFile.DirectoryName

                LogStart  = Get-Date

                _LastLine = ""

                _WarningsLogged = 0

                _ErrorsLogged = 0

                _VerboseMode = $PSBoundParameters.verbose -or $Host.Name -match 'ISE|Visual Studio'

                _Progress = [PSCustomObject] @{

                        ArrayID = 0

                        Counter = 0

                        Start   = $null

                        BarSec  = $progressbarsec

                        BarNext  = $null

                        LogFirst = $progresslogfirst

                        LogNext  = $null

                        LogMin   = $progresslogmin

                    }

    }

 

    if($logFile.new){

        Set-Content -Path $logFile.Fullname -Value "DateTime,Line,Type,Message"

    }

 

    $global:logging | Format-LogStringList -excludeProperty _* | FormatBorder | New-LogEntry

 

    if($scriptinvocation.BoundParameters.count){

        [PSCustomObject][hashtable]$scriptinvocation.BoundParameters | Format-LogStringList |

            FormatBorder -title "Bound Parameters:" -indentlevel 1 |

                New-LogEntry -indentlevel 1

    }

 

    if($ScriptImplicitParams = Get-Variable -Name ScriptImplicitParams -Scope Global -ErrorAction Ignore -ValueOnly){

        [PSCustomObject] $ScriptImplicitParams | Format-LogStringList |

            FormatBorder -title "Parameters with defaults:" -indentlevel 1 |

                New-LogEntry -indentlevel 1

    }

}

 Mielőtt beleásnék a függvény részleteibe, tegyünk egy kis kitérőt a jobb megértés kedvéért. Van egy Főszkript.ps1 szkriptem:

Import-Module C:\Users\Tibi\OneDrive\PSKönyv\Modul.psm1 -Force

 

$Változó = "Kakukk"

 

GetSzkriptVáltozó

És az ebben meghívott Modul.psm1 modulfájl:

$Változó = "Modul"

function GetSzkriptVáltozó {

    $Változó = "ModulFüggvény"

    Write-Host "Script:  $script:Változó"

    Write-Host "Sima:    $Változó"

    Write-Host "Global:  $global:változó"

    Write-Host "Scope 1: $(Get-Variable -Name változó -ValueOnly -Scope 1)"

    Write-Host "Scope 2: $(Get-Variable -Name változó -ValueOnly -Scope 2)"

}

Van tehát egy $változó a Főszkript-ben, van $változó a modulfájlban és $változó a modul GetSzkriptVáltozó függvényében is. A célom, hogy a GetSzkriptVáltozó függvénnyel írjam ki a Főszkript $változó-ját. Sajnos ez nem megy egyszerűen. Hiába próbálom az összes elképzelhető módon elérni a különböző scope-ok változóját, nem kapok „Kakukk” eredményt, ha a Főszkript.ps1-et nem dotsource módon hívom meg:

PS C:\> C:\Users\Tibi\OneDrive\PSKönyv\Főszkript.ps1

Script:  Modul

Sima:    ModulFüggvény

Global: 

Scope 1: Modul

Get-Variable : Cannot find a variable with the name 'változó'.

At C:\Users\Tibi\OneDrive\PSKönyv\Modul.psm1:8 char:28

+ ... ite-Host "Scope 2: $(Get-Variable -Name változó -ValueOnly -Scope 2)"

+                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : ObjectNotFound: (változó:String) [Get-Variable], ItemNotFoundException

    + FullyQualifiedErrorId : VariableNotFound,Microsoft.PowerShell.Commands.GetVariableCommand

 

Scope 2:

 A script scope a Modul $változó-ját jelenti, a „sima” $változó a ModulFüggvényé, Global $változó nincs, Scope 1 az valójában megint a Modul, Scope 2 a Global lenne, azaz az sincs. Azaz nem tudjuk elérni a szkript változóját a modul függvényéből! Márpedig nekünk az kellene, hogy a naplófájl inicializálása során a naplófájlba a fő szkript bizonyos adatait beírhassuk a naplózó modul függvényéből.

Szerencsénkre a Get-PSCallStack  cmdlet akár modul függvényéből meghívva átlátást biztosít a modul függvényéből a fő szkriptbe:

Command           Arguments Location            

-------           --------- --------            

GetSzkriptVáltozó {}        Modul.psm1: line 10 

Főszkript.ps1     {}        Főszkript.ps1: line 5

<ScriptBlock>     {}        <No file>           

Látható, hogy ennek a 2. (1-es indexű) eleme vonatkozik a Főszkript.ps1-re. Nézzük meg ezt részletesebben! Most a GetSzkriptVáltozó.psm1 csak ennyiből áll:

function GetSzkriptVáltozó {

    (Get-PSCallStack)[1]

}

A Főszkript.ps1 meg csak ennyi:

Import-Module C:\Users\Tibi\OneDrive\PSKönyv\Modul.psm1 -Force

 

GetSzkriptVáltozó | fl *

A Főszkript.ps1 futtatása ezt adja:

PS C:\> C:\Users\Tibi\OneDrive\PSKönyv\Főszkript.ps1

 

Command          : Főszkript.ps1

Location         : Főszkript.ps1: line 5

Arguments        : {}

ScriptName       : C:\Users\Tibi\OneDrive\PSKönyv\Főszkript.ps1

ScriptLineNumber : 5

InvocationInfo   : System.Management.Automation.InvocationInfo

Position         : GetSzkriptVáltozó | fl *

FunctionName     : <ScriptBlock>

Ennek az InvocationInfo tulajdonsága érdekel igazából:

PS C:\> C:\Users\Tibi\OneDrive\PSKönyv\Főszkript.ps1

 

MyCommand             : Főszkript.ps1

BoundParameters       : {}

UnboundArguments      : {}

ScriptLineNumber      : 1

OffsetInLine          : 1

HistoryId             : 21

ScriptName            :

Line                  : C:\Users\Tibi\OneDrive\PSKönyv\Főszkript.ps1

PositionMessage       : At line:1 char:1

                        + C:\Users\Tibi\OneDrive\PSKönyv\Főszkript.ps1

                        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

PSScriptRoot          :

PSCommandPath         :

InvocationName        : C:\Users\Tibi\OneDrive\PSKönyv\Főszkript.ps1

PipelineLength        : 1

PipelinePosition      : 1

ExpectingInput        : False

CommandOrigin         : Runspace

DisplayScriptPosition :

Szóval ez nem más, mint a Főszkript $MyInvocation változója. Ha visszamegyünk az Initialize-Logging függvényre, akkor a param blokk utáni első sora pont ezt tartalmazza:

    $scriptinvocation = (Get-PSCallStack)[1].InvocationInfo

 A következő sorban a globális szint $Error változóját ürítjük, majd a $scriptinvokation-ből kiszedem a fő szkript elérési útját ($path), a forráskódból a verziószámot ($version) és a publikálás dátumát ($releasedate), valamint a szkript nevét ($scriptname).  Előfordulhat, hogy még nem mentettem el a szkriptet, azaz még nincs is szkriptem, ilyenkor természetesen szkriptnév sincs, ilyenkor azt „interactive”-nak hívom. Ezután megcsináltatom a naplófájlt ($logFile) és a $global:logging változóba a szkript futásával kapcsolatos számos adatot teszek egy egyéni objektum tulajdonságain keresztül. Az aláhúzással kezdődő tulajdonságokat nem jelenítem majd meg a naplófájl fejlécében, ezek csak a naplózáshoz szükséges belső információk lesznek.

Ha új fájl a naplófájl, akkor berakom a CSV fejlécet: "DateTime,Line,Type,Message".  Majd a szépen formázott és keretezett szkriptadatokat. Ezután szintén keretezve a szkript hívásakor használt explicit paramétereket a $scriptinvocation.BoundParameters-ből.

Sajnos nehezebb dolgunk van a default értékekkel rendelkező paraméterekkel. Ezek nincsenek benne a BoundParameters-ben és nem is lehet elérni ezeket a modulból, így szükség van némi kooperációra a fő szkript részéről. A globális scope-ban definiált $implicitparams változón keresztül fogom majd átadni ezeket az adatokat az Initialize-Logging függvénynek, ezt majd később mutatom meg.

 

 

 

 



Word To HTML Converter