mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-07-16 00:40:39 +00:00
* Add startup performance tracing * Lazy initialize non-default tabs * Render install app entries incrementally * Defer and cache toggle status checks * Clean up runspace invocation ownership * Defer GUI runspace pool startup * Defer status taskbar asset rendering * Record final speed verification * Fix deferred install render timer callback * Add dispatcher smoke coverage for install rendering * Replace install render timer with dispatcher callbacks * Gate performance tracing behind compile switch * Clean up analyzer warnings in speed changes * Speed up runspace and tab initialization
552 lines
21 KiB
PowerShell
552 lines
21 KiB
PowerShell
#===========================================================================
|
|
# Tests - Config Files
|
|
#===========================================================================
|
|
|
|
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
$configRoot = Join-Path $PSScriptRoot "..\config"
|
|
$functionRoot = Join-Path $repoRoot "functions"
|
|
$xamlPath = Join-Path $repoRoot "xaml\inputXML.xaml"
|
|
$mainScriptPath = Join-Path $repoRoot "scripts\main.ps1"
|
|
$buttonScriptPath = Join-Path $repoRoot "functions\public\Invoke-WPFButton.ps1"
|
|
$configCases = @(
|
|
Get-ChildItem -Path $configRoot -Filter *.json | ForEach-Object {
|
|
@{
|
|
Name = $_.Name
|
|
Path = $_.FullName
|
|
}
|
|
}
|
|
)
|
|
|
|
BeforeAll {
|
|
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
$script:configRoot = Join-Path $script:repoRoot "config"
|
|
$script:functionRoot = Join-Path $script:repoRoot "functions"
|
|
$script:xamlPath = Join-Path $script:repoRoot "xaml\inputXML.xaml"
|
|
$script:mainScriptPath = Join-Path $script:repoRoot "scripts\main.ps1"
|
|
$script:buttonScriptPath = Join-Path $script:repoRoot "functions\public\Invoke-WPFButton.ps1"
|
|
|
|
function script:Get-WinUtilConfigObject {
|
|
param([string]$Name)
|
|
|
|
Get-Content -Path (Join-Path $script:configRoot "$Name.json") -Raw | ConvertFrom-Json
|
|
}
|
|
|
|
function script:Test-WinUtilHasProperty {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[psobject]$Object,
|
|
|
|
[Parameter(Mandatory)]
|
|
[string]$Name
|
|
)
|
|
|
|
return @($Object.PSObject.Properties.Name) -contains $Name
|
|
}
|
|
|
|
function script:Test-WinUtilHasNonEmptyProperty {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[psobject]$Object,
|
|
|
|
[Parameter(Mandatory)]
|
|
[string]$Name
|
|
)
|
|
|
|
if (-not (Test-WinUtilHasProperty -Object $Object -Name $Name)) {
|
|
return $false
|
|
}
|
|
|
|
$value = $Object.$Name
|
|
if ($null -eq $value) {
|
|
return $false
|
|
}
|
|
|
|
if ($value -is [string]) {
|
|
return -not [string]::IsNullOrWhiteSpace($value)
|
|
}
|
|
|
|
if ($value -is [array]) {
|
|
return @($value).Count -gt 0
|
|
}
|
|
|
|
return -not [string]::IsNullOrWhiteSpace([string]$value)
|
|
}
|
|
|
|
function script:Get-WinUtilMissingRequiredFields {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$EntryName,
|
|
|
|
[Parameter(Mandatory)]
|
|
[psobject]$Entry,
|
|
|
|
[Parameter(Mandatory)]
|
|
[string[]]$RequiredFields
|
|
)
|
|
|
|
foreach ($field in $RequiredFields) {
|
|
if (-not (Test-WinUtilHasNonEmptyProperty -Object $Entry -Name $field)) {
|
|
"$EntryName missing $field"
|
|
}
|
|
}
|
|
}
|
|
|
|
function script:Get-WinUtilTopLevelFunctionNames {
|
|
Get-ChildItem -Path $script:functionRoot -Filter *.ps1 -Recurse | ForEach-Object {
|
|
$tokens = $null
|
|
$syntaxErrors = $null
|
|
$ast = [System.Management.Automation.Language.Parser]::ParseFile($_.FullName, [ref]$tokens, [ref]$syntaxErrors)
|
|
if ($syntaxErrors.Count -ne 0) {
|
|
throw ($syntaxErrors | Out-String)
|
|
}
|
|
|
|
$ast.EndBlock.Statements |
|
|
Where-Object { $_ -is [System.Management.Automation.Language.FunctionDefinitionAst] } |
|
|
ForEach-Object { $_.Name }
|
|
} | Sort-Object -Unique
|
|
}
|
|
|
|
function script:Get-WinUtilButtonSwitchNames {
|
|
$buttonSource = Get-Content -Path $script:buttonScriptPath -Raw
|
|
[regex]::Matches($buttonSource, '"(WPF[A-Za-z0-9_]+)"\s*\{') |
|
|
ForEach-Object { $_.Groups[1].Value } |
|
|
Sort-Object -Unique
|
|
}
|
|
}
|
|
|
|
Describe "Config files" {
|
|
foreach ($configCase in $configCases) {
|
|
It "imports $($configCase.Name) with no JSON errors" -TestCases $configCase {
|
|
param([string]$Name, [string]$Path)
|
|
|
|
try {
|
|
Get-Content -Path $Path -Raw | ConvertFrom-Json | Out-Null
|
|
} catch {
|
|
throw "Failed to import ${Name}: $_"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "Applications config" {
|
|
$testCase = @{ Path = (Join-Path $configRoot "applications.json") }
|
|
|
|
It "contains at least one application" -TestCases $testCase {
|
|
param([string]$Path)
|
|
|
|
$applications = Get-Content -Path $Path -Raw | ConvertFrom-Json
|
|
$applicationEntries = @($applications.PSObject.Properties)
|
|
|
|
if ($applicationEntries.Count -eq 0) {
|
|
throw "applications.json does not contain any application entries."
|
|
}
|
|
}
|
|
|
|
It "contains required display fields and at least one install source" -TestCases $testCase {
|
|
param([string]$Path)
|
|
|
|
$applications = Get-Content -Path $Path -Raw | ConvertFrom-Json
|
|
$requiredFields = @("category", "content", "description", "link")
|
|
$invalidEntries = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($entry in $applications.PSObject.Properties) {
|
|
$entryFields = @($entry.Value.PSObject.Properties.Name)
|
|
|
|
foreach ($field in $requiredFields) {
|
|
if ($entryFields -notcontains $field -or [string]::IsNullOrWhiteSpace([string]$entry.Value.$field)) {
|
|
$invalidEntries.Add("$($entry.Name) missing $field")
|
|
}
|
|
}
|
|
|
|
$hasInstallSource = $false
|
|
foreach ($sourceField in @("winget", "choco")) {
|
|
if ($entryFields -contains $sourceField -and -not [string]::IsNullOrWhiteSpace([string]$entry.Value.$sourceField)) {
|
|
$hasInstallSource = $true
|
|
}
|
|
}
|
|
|
|
if (-not $hasInstallSource) {
|
|
$invalidEntries.Add("$($entry.Name) missing winget/choco install source")
|
|
}
|
|
}
|
|
|
|
if ($invalidEntries.Count -gt 0) {
|
|
throw ($invalidEntries -join "`n")
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "Tweaks config" {
|
|
$testCase = @{ Path = (Join-Path $configRoot "tweaks.json") }
|
|
|
|
It "contains undo metadata for registry and service actions" -TestCases $testCase {
|
|
param([string]$Path)
|
|
|
|
$tweaks = Get-Content -Path $Path -Raw | ConvertFrom-Json
|
|
$invalidTweaks = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($tweak in $tweaks.PSObject.Properties) {
|
|
foreach ($registryEntry in @($tweak.Value.registry)) {
|
|
if ($null -eq $registryEntry) { continue }
|
|
|
|
if ($registryEntry.PSObject.Properties.Name -notcontains "OriginalValue" -or
|
|
[string]::IsNullOrWhiteSpace([string]$registryEntry.OriginalValue)) {
|
|
$invalidTweaks.Add("$($tweak.Name),registry")
|
|
}
|
|
}
|
|
|
|
foreach ($serviceEntry in @($tweak.Value.service)) {
|
|
if ($null -eq $serviceEntry) { continue }
|
|
|
|
if ($serviceEntry.PSObject.Properties.Name -notcontains "OriginalType" -or
|
|
[string]::IsNullOrWhiteSpace([string]$serviceEntry.OriginalType)) {
|
|
$invalidTweaks.Add("$($tweak.Name),service")
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($invalidTweaks.Count -gt 0) {
|
|
throw ($invalidTweaks -join "`n")
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "Preset config" {
|
|
It "references existing config entries or supported actions" {
|
|
$preset = Get-WinUtilConfigObject -Name "preset"
|
|
$applications = Get-WinUtilConfigObject -Name "applications"
|
|
$tweaks = Get-WinUtilConfigObject -Name "tweaks"
|
|
$feature = Get-WinUtilConfigObject -Name "feature"
|
|
$appx = Get-WinUtilConfigObject -Name "appx"
|
|
|
|
$validReferences = @(
|
|
$tweaks.PSObject.Properties.Name
|
|
$feature.PSObject.Properties.Name
|
|
$appx.PSObject.Properties.Name
|
|
$applications.PSObject.Properties.Name | ForEach-Object { "WPFInstall$_" }
|
|
Get-WinUtilButtonSwitchNames
|
|
) | Sort-Object -Unique
|
|
|
|
$invalidReferences = New-Object System.Collections.Generic.List[string]
|
|
foreach ($presetEntry in $preset.PSObject.Properties) {
|
|
foreach ($reference in @($presetEntry.Value)) {
|
|
if ($validReferences -notcontains $reference) {
|
|
$invalidReferences.Add("$($presetEntry.Name) references missing item $reference")
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($invalidReferences.Count -gt 0) {
|
|
throw ($invalidReferences -join "`n")
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "App navigation config" {
|
|
It "is wired to an existing XAML target grid" {
|
|
$mainScript = Get-Content -Path $script:mainScriptPath -Raw
|
|
$tabInitializerScript = Get-Content -Path (Join-Path $script:repoRoot "functions/private/Initialize-WinUtilTabContent.ps1") -Raw
|
|
$targetGridMatch = [regex]::Match(
|
|
"$mainScript`n$tabInitializerScript",
|
|
'Invoke-WPFUIElements\s+-configVariable\s+\$sync\.configs\.appnavigation\s+-targetGridName\s+"([^"]+)"'
|
|
)
|
|
|
|
if (-not $targetGridMatch.Success) {
|
|
throw "Startup tab initialization does not wire appnavigation through Invoke-WPFUIElements."
|
|
}
|
|
|
|
$xamlText = Get-Content -Path $script:xamlPath -Raw
|
|
$targetGridName = $targetGridMatch.Groups[1].Value
|
|
if ($xamlText -notmatch "Name=`"$([regex]::Escape($targetGridName))`"") {
|
|
throw "appnavigation target grid '$targetGridName' was not found in xaml/inputXML.xaml."
|
|
}
|
|
}
|
|
|
|
It "contains renderable entries with valid button and radio groups" {
|
|
$appnavigation = Get-WinUtilConfigObject -Name "appnavigation"
|
|
$feature = Get-WinUtilConfigObject -Name "feature"
|
|
$requiredFields = @("Content", "Category", "Type", "Order", "Description")
|
|
$allowedTypes = @("Button", "RadioButton", "Note")
|
|
$supportedButtons = @(
|
|
Get-WinUtilButtonSwitchNames
|
|
$feature.PSObject.Properties.Name
|
|
) | Sort-Object -Unique
|
|
$invalidEntries = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($entry in $appnavigation.PSObject.Properties) {
|
|
foreach ($missingField in (Get-WinUtilMissingRequiredFields -EntryName $entry.Name -Entry $entry.Value -RequiredFields $requiredFields)) {
|
|
$invalidEntries.Add($missingField)
|
|
}
|
|
|
|
if ($allowedTypes -notcontains $entry.Value.Type) {
|
|
$invalidEntries.Add("$($entry.Name) has unsupported Type '$($entry.Value.Type)'")
|
|
}
|
|
|
|
if ($entry.Value.Type -eq "Button" -and $supportedButtons -notcontains $entry.Name) {
|
|
$invalidEntries.Add("$($entry.Name) is not handled by Invoke-WPFButton or feature config")
|
|
}
|
|
|
|
if ($entry.Value.Type -eq "RadioButton") {
|
|
if (-not (Test-WinUtilHasNonEmptyProperty -Object $entry.Value -Name "GroupName")) {
|
|
$invalidEntries.Add("$($entry.Name) missing GroupName")
|
|
}
|
|
|
|
if (-not (Test-WinUtilHasProperty -Object $entry.Value -Name "Checked")) {
|
|
$invalidEntries.Add("$($entry.Name) missing Checked")
|
|
}
|
|
}
|
|
}
|
|
|
|
$radioButtons = @($appnavigation.PSObject.Properties | Where-Object { $_.Value.Type -eq "RadioButton" })
|
|
foreach ($group in ($radioButtons | Group-Object -Property { $_.Value.GroupName })) {
|
|
if ([string]::IsNullOrWhiteSpace($group.Name)) {
|
|
$invalidEntries.Add("RadioButton group name is blank")
|
|
continue
|
|
}
|
|
|
|
$checkedCount = @($group.Group | Where-Object { $_.Value.Checked -eq $true }).Count
|
|
if ($checkedCount -ne 1) {
|
|
$invalidEntries.Add("RadioButton group '$($group.Name)' has $checkedCount checked entries")
|
|
}
|
|
}
|
|
|
|
if ($invalidEntries.Count -gt 0) {
|
|
throw ($invalidEntries -join "`n")
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "UI-rendered config entries" {
|
|
It "contains required AppX fields" {
|
|
$appx = Get-WinUtilConfigObject -Name "appx"
|
|
$requiredFields = @("Category", "Content", "Description", "Panel", "PackageId")
|
|
$invalidEntries = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($entry in $appx.PSObject.Properties) {
|
|
foreach ($missingField in (Get-WinUtilMissingRequiredFields -EntryName $entry.Name -Entry $entry.Value -RequiredFields $requiredFields)) {
|
|
$invalidEntries.Add($missingField)
|
|
}
|
|
}
|
|
|
|
if ($invalidEntries.Count -gt 0) {
|
|
throw ($invalidEntries -join "`n")
|
|
}
|
|
}
|
|
|
|
It "contains required DNS fields with parseable IP addresses" {
|
|
$dns = Get-WinUtilConfigObject -Name "dns"
|
|
$requiredFields = @("Primary", "Secondary", "Primary6", "Secondary6")
|
|
$invalidEntries = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($entry in $dns.PSObject.Properties) {
|
|
foreach ($missingField in (Get-WinUtilMissingRequiredFields -EntryName $entry.Name -Entry $entry.Value -RequiredFields $requiredFields)) {
|
|
$invalidEntries.Add($missingField)
|
|
}
|
|
|
|
foreach ($field in $requiredFields) {
|
|
if (-not (Test-WinUtilHasNonEmptyProperty -Object $entry.Value -Name $field)) {
|
|
continue
|
|
}
|
|
|
|
try {
|
|
[System.Net.IPAddress]::Parse([string]$entry.Value.$field) | Out-Null
|
|
} catch {
|
|
$invalidEntries.Add("$($entry.Name) $field is not a parseable IP address")
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($invalidEntries.Count -gt 0) {
|
|
throw ($invalidEntries -join "`n")
|
|
}
|
|
}
|
|
|
|
It "contains required feature fields and valid configured functions" {
|
|
$feature = Get-WinUtilConfigObject -Name "feature"
|
|
$functionNames = Get-WinUtilTopLevelFunctionNames
|
|
$requiredFields = @("Content", "category", "panel", "link")
|
|
$invalidEntries = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($entry in $feature.PSObject.Properties) {
|
|
foreach ($missingField in (Get-WinUtilMissingRequiredFields -EntryName $entry.Name -Entry $entry.Value -RequiredFields $requiredFields)) {
|
|
$invalidEntries.Add($missingField)
|
|
}
|
|
|
|
if ($entry.Value.Type -and $entry.Value.Type -ne "Button") {
|
|
$invalidEntries.Add("$($entry.Name) has unsupported Type '$($entry.Value.Type)'")
|
|
}
|
|
|
|
if ($entry.Value.Type -eq "Button") {
|
|
if (-not $entry.Value.function -and -not $entry.Value.InvokeScript) {
|
|
$invalidEntries.Add("$($entry.Name) button missing function or InvokeScript")
|
|
}
|
|
} else {
|
|
if (-not (Test-WinUtilHasNonEmptyProperty -Object $entry.Value -Name "Description")) {
|
|
$invalidEntries.Add("$($entry.Name) missing Description")
|
|
}
|
|
|
|
if (-not $entry.Value.feature -and -not $entry.Value.InvokeScript) {
|
|
$invalidEntries.Add("$($entry.Name) missing feature or InvokeScript action")
|
|
}
|
|
}
|
|
|
|
if ($entry.Value.function -and $functionNames -notcontains $entry.Value.function) {
|
|
$invalidEntries.Add("$($entry.Name) references missing function $($entry.Value.function)")
|
|
}
|
|
}
|
|
|
|
if ($invalidEntries.Count -gt 0) {
|
|
throw ($invalidEntries -join "`n")
|
|
}
|
|
}
|
|
|
|
It "contains required tweak fields and valid action metadata" {
|
|
$tweaks = Get-WinUtilConfigObject -Name "tweaks"
|
|
$requiredFields = @("Content", "category", "panel", "link")
|
|
$allowedTypes = @("Button", "Combobox", "Toggle", "ToggleButton")
|
|
$supportedButtons = Get-WinUtilButtonSwitchNames
|
|
$invalidEntries = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($entry in $tweaks.PSObject.Properties) {
|
|
foreach ($missingField in (Get-WinUtilMissingRequiredFields -EntryName $entry.Name -Entry $entry.Value -RequiredFields $requiredFields)) {
|
|
$invalidEntries.Add($missingField)
|
|
}
|
|
|
|
if ($entry.Value.Type -and $allowedTypes -notcontains $entry.Value.Type) {
|
|
$invalidEntries.Add("$($entry.Name) has unsupported Type '$($entry.Value.Type)'")
|
|
}
|
|
|
|
if ($entry.Value.Type -eq "Button") {
|
|
if ($supportedButtons -notcontains $entry.Name) {
|
|
$invalidEntries.Add("$($entry.Name) is not handled by Invoke-WPFButton")
|
|
}
|
|
} elseif ($entry.Value.Type -eq "Combobox") {
|
|
if (-not (Test-WinUtilHasNonEmptyProperty -Object $entry.Value -Name "ComboItems")) {
|
|
$invalidEntries.Add("$($entry.Name) combobox missing ComboItems")
|
|
}
|
|
} else {
|
|
if (-not (Test-WinUtilHasNonEmptyProperty -Object $entry.Value -Name "Description")) {
|
|
$invalidEntries.Add("$($entry.Name) missing Description")
|
|
}
|
|
|
|
if (-not $entry.Value.registry -and -not $entry.Value.service -and -not $entry.Value.InvokeScript -and -not $entry.Value.appx) {
|
|
$invalidEntries.Add("$($entry.Name) missing registry, service, InvokeScript, or appx action")
|
|
}
|
|
}
|
|
|
|
foreach ($registryEntry in @($entry.Value.registry)) {
|
|
if ($null -eq $registryEntry) { continue }
|
|
|
|
foreach ($missingField in (Get-WinUtilMissingRequiredFields -EntryName "$($entry.Name),registry" -Entry $registryEntry -RequiredFields @("Path", "Name", "Type", "Value", "OriginalValue"))) {
|
|
$invalidEntries.Add($missingField)
|
|
}
|
|
}
|
|
|
|
foreach ($serviceEntry in @($entry.Value.service)) {
|
|
if ($null -eq $serviceEntry) { continue }
|
|
|
|
foreach ($missingField in (Get-WinUtilMissingRequiredFields -EntryName "$($entry.Name),service" -Entry $serviceEntry -RequiredFields @("Name", "StartupType", "OriginalType"))) {
|
|
$invalidEntries.Add($missingField)
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($invalidEntries.Count -gt 0) {
|
|
throw ($invalidEntries -join "`n")
|
|
}
|
|
}
|
|
|
|
It "defines theme resources required by XAML rendering" {
|
|
$themes = Get-WinUtilConfigObject -Name "themes"
|
|
$invalidEntries = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($themeName in @("shared", "Light", "Dark")) {
|
|
if (-not (Test-WinUtilHasProperty -Object $themes -Name $themeName)) {
|
|
$invalidEntries.Add("themes.json missing $themeName")
|
|
continue
|
|
}
|
|
|
|
foreach ($property in $themes.$themeName.PSObject.Properties) {
|
|
if ([string]::IsNullOrWhiteSpace([string]$property.Value)) {
|
|
$invalidEntries.Add("$themeName.$($property.Name) is blank")
|
|
}
|
|
}
|
|
}
|
|
|
|
$lightKeys = @($themes.Light.PSObject.Properties.Name)
|
|
$darkKeys = @($themes.Dark.PSObject.Properties.Name)
|
|
foreach ($key in $lightKeys) {
|
|
if ($darkKeys -notcontains $key) {
|
|
$invalidEntries.Add("Dark theme missing $key")
|
|
}
|
|
}
|
|
foreach ($key in $darkKeys) {
|
|
if ($lightKeys -notcontains $key) {
|
|
$invalidEntries.Add("Light theme missing $key")
|
|
}
|
|
}
|
|
|
|
$xamlText = Get-Content -Path $script:xamlPath -Raw
|
|
$dynamicResourceNames = @(
|
|
[regex]::Matches($xamlText, '\{DynamicResource\s+([^\}\s]+)') |
|
|
ForEach-Object { $_.Groups[1].Value }
|
|
) | Sort-Object -Unique
|
|
$xamlDefinedResourceNames = @(
|
|
[regex]::Matches($xamlText, 'x:Key="([^"]+)"') |
|
|
ForEach-Object { $_.Groups[1].Value }
|
|
) | Sort-Object -Unique
|
|
$themeResourceNames = @(
|
|
$themes.shared.PSObject.Properties.Name
|
|
$themes.Light.PSObject.Properties.Name
|
|
$themes.Dark.PSObject.Properties.Name
|
|
"CBorderColor"
|
|
"CButtonBackgroundMouseoverColor"
|
|
) | Sort-Object -Unique
|
|
|
|
foreach ($resourceName in $dynamicResourceNames) {
|
|
if ($xamlDefinedResourceNames -notcontains $resourceName -and $themeResourceNames -notcontains $resourceName) {
|
|
$invalidEntries.Add("XAML DynamicResource '$resourceName' is not defined in themes.json or XAML resources")
|
|
}
|
|
}
|
|
|
|
if ($invalidEntries.Count -gt 0) {
|
|
throw ($invalidEntries -join "`n")
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "Embedded config scripts" {
|
|
It "parse as PowerShell scriptblocks" {
|
|
$invalidScripts = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($configFile in (Get-ChildItem -Path $script:configRoot -Filter *.json)) {
|
|
$config = Get-Content -Path $configFile.FullName -Raw | ConvertFrom-Json
|
|
foreach ($entry in $config.PSObject.Properties) {
|
|
foreach ($field in @("InvokeScript", "UndoScript")) {
|
|
if (-not (Test-WinUtilHasProperty -Object $entry.Value -Name $field)) {
|
|
continue
|
|
}
|
|
|
|
$index = 0
|
|
foreach ($scriptText in @($entry.Value.$field)) {
|
|
$index++
|
|
if ([string]::IsNullOrWhiteSpace([string]$scriptText)) {
|
|
continue
|
|
}
|
|
|
|
try {
|
|
[scriptblock]::Create([string]$scriptText) | Out-Null
|
|
} catch {
|
|
$invalidScripts.Add("$($configFile.Name):$($entry.Name).$field[$index] $($psitem.Exception.Message)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($invalidScripts.Count -gt 0) {
|
|
throw ($invalidScripts -join "`n")
|
|
}
|
|
}
|
|
}
|