Ha megnézzük ezt a PSD1 fájl tartalmát láthatjuk, hogy a 3 benne található szekció más célt szolgál. A ScriptConfig rész olyan adatokat tartalmaz, ami a szkriptünk saját információit tartalmazza. Az AccessToRESTApi-ban egy webszolgáltatás eléréséhez szükséges adatok vannak, ezt a részt több szkript is használhatja, így ezeket az adatokat jó lenne egy külön fájlba rakni, hogy elég legyen egy helyen karbantartani az adatokat. A LogSettings részben olyan adatok vannak, ami akár az összes szkriptünkre érvényes lehet, így azokat egy harmadik fájlban kellene tárolni. Lássuk ezeket a fájlokat! Az első a fő szkriptünkhöz tartozó PSD1 fájl:
# Section05-Video4Script.ps1.psd1
@{
# This
section is used by this specific script
Action = {$script:PSEnvironment = 'DEV'}
ScriptConfig
= @{
textvalue = "SomeText"
date = {[datetime] "2022.12.01"}
bool = $true
}
Conditional_1_Prod = @{
Condition = {$env:COMPUTERNAME -eq 'ProdServer'}
Action = {$script:PSEnvironment = 'PROD'}
ScriptConfig = @{
textvalue = "ProdText"
}
UniqueToProd = @{
ProdValue = "This is the unique
value"
}
}
Conditional_2_PreProd = @{
condition = {$true}
ScriptConfig = @{
textvalue = "PreProdText"
}
}
}
Látható, hogy itt csak tényleg a szkript saját beállításai vannak már, se a RESTApi dolgok, se a globális beállítások nincsenek itt. Van viszont egy új adattípus, az „Action”. Ezek csak azért vannak, hogy a szkriptblokkok feloldásakor ezek végrehajtódjanak, de ténylegesen nem kell majd eltárolni a konfigurációs adatok közé. Ezekben az Action bejegyzésekben elsősorban az aktuális környezet típusát határozom meg és tárolom el egy szkriptszintű változóba, hogy a többi, közös konfigurációs fájl feltételes részei számára tudjuk szolgáltatni a bemeneti adatokat, mint például „DEV” vagy „PROD”. Így majd például a RESTApi konfigurációs fájlban ezalapján lesz kiválasztva, hogy éppen mely feltételes részek aktiválódjanak. Mivel a többi konfigurációs fájlt más szkriptek is használják, így oda nem tehetünk „beégetett” feltételeket, mert ami az egyik szkript számára PROD, az a másik számára DEV vagy akár ismeretlen.
Nézzük tehát, hogy ez alapján hogyan néz ki a RestAPIConfig.psd1:
# RestAPIConfig.psd1
@{
# This
should be shared between other scripts
AccessToRESTApi = @{
AppID = 'd24099e5-10cb-4388-8ef4-f1c9f374d50b'
Token = 'UG93ZXJTaGVsbElzR3JlYXQh'
ApiURL = 'https://api.myapp.com/rest'
}
Conditional_1_Prod = @{
Condition = {$script:PSEnvironment -eq 'PROD'}
AccessToRESTApi = @{
AppID = 'd24099e5-10cb-4388-1111-f1c9f374d50b'
Token = 'prodprodprod'
ApiURL = 'https://apiprod.myapp.com/rest'
}
}
}
Látszik, hogy ebben már a feltételes részben az előbb látott $script:PSEnvironment változó szerepel. A globalconfig.psd1 egyszerűbb az én esetemben, itt nincs feltételes rész:
# globalconfig.psd1
@{
# This is
the default setting for all scripts
LogSettings = @{
LogFolder = {join-path $PSScriptMyRoot Logs}
subhive = @{
subdynamicdata = {[math]::Sqrt(9)}
}
}
}
Egészítsük tehát ki a ParseConfig függvényünket, hogy több konfigurációs fájlt is képes legyen beolvasni, illetve az új Action bejegyzésekkel is elbánjon:
# This is a production script
#region Functions
function ParseConfig {
param(
[string[]]$paths
)
function ResolveDynamicData {
param([hashtable] $confighive)
foreach($key in ($confighive.Clone().Keys | Sort-Object -Property {
if($_ -match '^Condition$'){"zz$($_)"}
elseif($_ -match 'Action'){"zzz$($_)"}
elseif($_ -match '^Conditional_'){"zzzz$($_)"}
else{"__$($_)"}
}
)
){
if($confighive.$key -is [hashtable]){
ResolveDynamicData -confighive $confighive.$key
}
elseif($confighive.$key -is [scriptblock] -and (!$confighive.ContainsKey('Condition') -or $confighive.Condition)){
$confighive.$key = &(& $confighive.$key)
}
}
}
function MergeHives {
param(
[hashtable] $hive,
[hashtable] $target = $PSConfig
)
foreach($h in $hive.Clone().Getenumerator()){
if($h.key -match '^Condition|^action$'){
continue
}
elseif($h.value -isnot [hashtable]){
$target.($h.key) = $h.value
}
elseif(!$target.ContainsKey($h.key)){
if($h.value.containskey('action')){
$h.value.remove('action')
}
$target.($h.key) = $h.value
}
else{
MergeHives -hive $h.value -target $target.($h.key)
}
}
}
if($paths -notcontains "$($PSScriptFullPath).psd1" -and (Test-Path "$($PSScriptFullPath).psd1")){
$paths = @("$($PSScriptFullPath).psd1") + $paths
}
$PSConfig = @{}
foreach($path in $paths){
if(!(Test-Path -Path $path)){
Write-Error "No config file was found at
'$path'"
continue
}
$config = Import-PowerShellDataFile -Path $path
ResolveDynamicData -confighive $Config
$ConfigClone = $Config.Clone()
foreach($key in ($ConfigClone.keys -notmatch '^Condition' | Sort-Object)){
MergeHives -hive $Config
}
foreach($key in ($ConfigClone.keys -match '^Conditional_' | Sort-Object)){
if($ConfigClone.$key.condition){
MergeHives -hive $Config.$key
}
}
}
$PSConfig
}
#endregion
#########################################################
#
# Body
#
#########################################################
$PSScriptFullPath = $myinvocation.mycommand.path
$PSScriptMyRoot = $PSScriptRoot
$PSConfig = ParseConfig -paths "$PSScriptMyRoot\globalconfig.psd1", "$PSScriptMyRoot\RestAPIConfig.psd1"
Nézzük először a függvény törzsét! A $path paraméterben immár több elérési út is lehet, így azokat majd egy foreach ciklussal fogom feldolgozni. Előtte megnézem, hogy a szkript nevének megfelelő PSD1 fájl a $path-ban szerepel-e és ha nem, viszont megtalálható a szkript mappájában, a szkript mellett, akkor azt még hozzárakja a $path értékeihez a legelső helyre, hogy mindenképpen az legyen kiértékelve elsőként. Ezután jön a foreach, mellyel minden konfigurációs fájlt importálok, majd feloldom bennük a szkriptblokkokat majd beintegrálom a MergeHives-al a nem Condition kezdetű kulcsokat a $PSConfig változóba, majd azokat a Conditional_ kulcsokat, amelyeknek a Condition ága igazra értékelődött ki.
A ResolveDynamicData-ban annyi változtatás lett, hogy most fontos most a kulcsok sorrendje, ezt a Sort-Object-nek átadott dinamikus tulajdonság-definícióval oldottam meg. Ez úgy működik, hogy a „Condition” nevű kulcsok neve elé „zz”-t tesz, az „Action” kulcsok elé „zzz”, a „Conditional_*” nevűek elé „zzzz”-t, az összes többi kulcs neve elé „__”-t. Ezzel azt érem el, hogy először az „egyéb” kulcsokat oldja fel, majd a feltételeket, majd az akciókat és végül a feltételes blokkok belsejét, így csak azokat az akciókat hajtja végre, ahol már a feltétel igazra értékelődött ki.
Ha futtatom a szkriptet az eredeti feltételek szerint (a gépem neve nem ProdServer), akkor ezt kapom:
PS C:\> . C:\PSTools\Section05-Video4Script.ps1
PS C:\> $PSEnvironment
DEV
PS C:\> $PSConfig.AccessToRESTApi
Name Value
---- -----
AppID d24099e5-10cb-4388-8ef4-f1c9f374d50b
Token UG93ZXJTaGVsbElzR3JlYXQh
ApiURL https://api.myapp.com/rest
Ha viszont $true-t teszek a Conditional_1_Prod feltételébe, akkor ezt:
PS C:\> . C:\PSTools\Section05-Video4Script.ps1
PS C:\> $PSEnvironment
PROD
PS C:\> $PSConfig.AccessToRESTApi
Name Value
---- -----
AppID d24099e5-10cb-4388-1111-f1c9f374d50b
Token prodprodprod
Ezzel elértük, hogy a konfigurációs fájlok a feltételek alapján automatikusan állítják össze az összes beállítási adatot, se a szkripthez, de még csak a konfigurációs fájlokhoz sem kell nyúlnunk, így az változtatásokból eredő hibalehetőségek valószínűségét gyakorlatilag nullára csökkentettük.