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.