FITS.IniFileUtils.psm1
|
# Load localized strings — automatic fallback to en-US if culture file is missing Import-LocalizedData -BindingVariable script:Strings -FileName FITS.IniFileUtils -ErrorAction SilentlyContinue if (-not $script:Strings) { Import-LocalizedData -BindingVariable script:Strings -FileName FITS.IniFileUtils ` -UICulture 'en-US' -ErrorAction SilentlyContinue } $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Write-Verbose $script:Strings.ModuleRemoved Remove-Variable -Name Strings -Scope Script -ErrorAction SilentlyContinue } # ───────────────────────────────────────────────────────────────────────────── # Get-IniContent # ───────────────────────────────────────────────────────────────────────────── function Get-IniContent { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding()] [OutputType([System.Collections.Specialized.OrderedDictionary])] param( [Parameter(Mandatory, Position = 0)] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string] $FilePath ) # Case-SENSITIVE keys — intentional: INI files may have keys that differ # only in case (e.g. some Exchange / application config files). $ini = New-Object System.Collections.Specialized.OrderedDictionary $section = '_global' $commentCount = 0 $ini[$section] = New-Object System.Collections.Specialized.OrderedDictionary switch -regex -file $FilePath { '^\[(.+)\]' { $section = $matches[1] $commentCount = 0 $ini[$section] = New-Object System.Collections.Specialized.OrderedDictionary Write-Verbose ($script:Strings.VerboseSection -f $section) } '^(;.*)$' { $commentCount++ $ini[$section]["Comment$commentCount"] = $matches[1] Write-Verbose ($script:Strings.VerboseComment -f $matches[1]) } '(.+?)\s*=(.*)' { $name, $value = $matches[1..2] $ini[$section][$name] = $value.Trim() Write-Verbose ($script:Strings.VerboseKey -f $name, $value.Trim()) } } if ($ini['_global'].Count -eq 0) { $ini.Remove('_global') } return $ini } # ───────────────────────────────────────────────────────────────────────────── # New-IniFileContentString # ───────────────────────────────────────────────────────────────────────────── function New-IniFileContentString { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $IniFileContent ) $sb = New-Object System.Text.StringBuilder $firstSection = $true foreach ($section in $IniFileContent.Keys) { if (-not $firstSection) { $sb.AppendLine('') | Out-Null } $sb.AppendLine("[$section]") | Out-Null foreach ($key in $IniFileContent[$section].Keys) { if ($key -match '^Comment\d+$') { $sb.AppendLine($IniFileContent[$section][$key]) | Out-Null } else { $sb.AppendLine("$key = $($IniFileContent[$section][$key])") | Out-Null } } $firstSection = $false } return $sb.ToString() } # ───────────────────────────────────────────────────────────────────────────── # New-IniFileSection # ───────────────────────────────────────────────────────────────────────────── function New-IniFileSection { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding(DefaultParameterSetName = 'Append')] param( [Parameter(Mandatory, Position = 0)] [string] $SectionName, [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $IniFileContent, [Parameter(ParameterSetName = 'InsertAfter')] [string] $InsertAfter, [Parameter(ParameterSetName = 'InsertBefore')] [string] $InsertBefore ) if ($IniFileContent.Contains($SectionName)) { Write-Warning ($script:Strings.SectionAlreadyExists -f $SectionName) return } $newSection = New-Object System.Collections.Specialized.OrderedDictionary switch ($PSCmdlet.ParameterSetName) { 'InsertAfter' { if (-not $IniFileContent.Contains($InsertAfter)) { Write-Warning ($script:Strings.RefSectionNotFound -f $InsertAfter) $IniFileContent[$SectionName] = $newSection return } $index = $IniFileContent.Keys.IndexOf($InsertAfter) + 1 $IniFileContent.Insert($index, $SectionName, $newSection) Write-Verbose ($script:Strings.SectionInsertedAfter -f $SectionName, $InsertAfter, $index) } 'InsertBefore' { if (-not $IniFileContent.Contains($InsertBefore)) { Write-Warning ($script:Strings.RefSectionNotFound -f $InsertBefore) $IniFileContent[$SectionName] = $newSection return } $index = $IniFileContent.Keys.IndexOf($InsertBefore) $IniFileContent.Insert($index, $SectionName, $newSection) Write-Verbose ($script:Strings.SectionInsertedBefore -f $SectionName, $InsertBefore, $index) } default { $IniFileContent[$SectionName] = $newSection Write-Verbose ($script:Strings.SectionAppended -f $SectionName) } } } # ───────────────────────────────────────────────────────────────────────────── # Add-ValueToIniFileContent # ───────────────────────────────────────────────────────────────────────────── function Add-ValueToIniFileContent { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $SectionName, [Parameter(Mandatory)] [string[]] $ValueName, [Parameter(Mandatory)] [string] $Value, [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $IniFileContent, [switch] $AppendValue ) if (-not $IniFileContent.Contains($SectionName)) { Write-Warning ($script:Strings.SectionNotFound -f $SectionName) return } $valueList = New-Object System.Collections.Generic.List``1[System.String] foreach ($keyName in $ValueName) { if ($IniFileContent[$SectionName].Contains($keyName) -and $AppendValue) { $existing = $IniFileContent[$SectionName][$keyName] if (-not [string]::IsNullOrWhiteSpace($existing)) { $valueList.AddRange([string[]]($existing -split ',')) } } $valueList.Add($Value) $IniFileContent[$SectionName][$keyName] = $valueList -join ',' $valueList.Clear() } } # ───────────────────────────────────────────────────────────────────────────── # Remove-ValueFromIniFileContent # ───────────────────────────────────────────────────────────────────────────── function Remove-ValueFromIniFileContent { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $SectionName, [Parameter(Mandatory, ParameterSetName = 'SpecificValue')] [Parameter(Mandatory, ParameterSetName = 'ValueExpression')] [string[]] $ValueName, [Parameter(Mandatory, ParameterSetName = 'SpecificValue')] [Parameter(Mandatory, ParameterSetName = 'ValueForAllKeys')] [string] $Value, [Parameter(Mandatory, ParameterSetName = 'ValueExpression')] [Parameter(Mandatory, ParameterSetName = 'ValueExpressionForAllKeys')] [string] $ValueRegEx, [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $IniFileContent, [switch] $RemoveSettingIfEmpty, [Parameter(Mandatory, ParameterSetName = 'ValueExpressionForAllKeys')] [Parameter(Mandatory, ParameterSetName = 'ValueForAllKeys')] [switch] $RemoveValueFromAllKeys ) if (-not $IniFileContent.Contains($SectionName)) { Write-Warning ($script:Strings.SectionNotFound -f $SectionName) return } $valueList = New-Object System.Collections.Generic.List``1[System.String] $targetKeys = if ($PSCmdlet.ParameterSetName -match 'ForAllKeys') { [string[]]($IniFileContent[$SectionName].Keys) } else { $ValueName } $filter = if ($PSCmdlet.ParameterSetName -match 'ValueExpression') { [scriptblock]::Create("`$args -imatch '$ValueRegEx'") } else { [scriptblock]::Create("`$args -ieq `"$Value`"") } foreach ($keyName in $targetKeys) { if (-not $IniFileContent[$SectionName].Contains($keyName)) { continue } $existing = $IniFileContent[$SectionName][$keyName] if (-not [string]::IsNullOrWhiteSpace($existing)) { $valueList.AddRange([string[]]($existing -split ',')) } $filtered = @($valueList | Where-Object { -not (& $filter $_) }) $valueList.Clear() if ($filtered.Count -gt 0) { $valueList.AddRange([string[]]$filtered) } if ($valueList.Count -eq 0 -and $RemoveSettingIfEmpty) { $IniFileContent[$SectionName].Remove($keyName) } else { $IniFileContent[$SectionName][$keyName] = $valueList -join ',' } $valueList.Clear() } } # ───────────────────────────────────────────────────────────────────────────── # Remove-IniFileSection # ───────────────────────────────────────────────────────────────────────────── function Remove-IniFileSection { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory, Position = 0)] [string] $SectionName, [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $IniFileContent, [switch] $RemoveAllValues ) if (-not $IniFileContent.Contains($SectionName)) { Write-Warning ($script:Strings.SectionNotFound -f $SectionName) return } $entryCount = $IniFileContent[$SectionName].Count if ($entryCount -gt 0 -and -not $RemoveAllValues) { Write-Warning ($script:Strings.SectionNotEmpty -f $SectionName, $entryCount) return } if ($PSCmdlet.ShouldProcess($SectionName, $script:Strings.ShouldProcessRemoveSection)) { $IniFileContent.Remove($SectionName) Write-Verbose ($script:Strings.SectionRemoved -f $SectionName, $entryCount) } } # ───────────────────────────────────────────────────────────────────────────── # Clear-IniFileSection # ───────────────────────────────────────────────────────────────────────────── function Clear-IniFileSection { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory, Position = 0)] [string] $SectionName, [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $IniFileContent ) if (-not $IniFileContent.Contains($SectionName)) { Write-Warning ($script:Strings.SectionNotFound -f $SectionName) return } if ($PSCmdlet.ShouldProcess($SectionName, $script:Strings.ShouldProcessClearSection)) { $count = $IniFileContent[$SectionName].Count $IniFileContent[$SectionName].Clear() Write-Verbose ($script:Strings.SectionCleared -f $SectionName, $count) } } # ───────────────────────────────────────────────────────────────────────────── # Move-IniFileSection # ───────────────────────────────────────────────────────────────────────────── function Move-IniFileSection { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding(DefaultParameterSetName = 'MoveAfter')] param( [Parameter(Mandatory, Position = 0)] [string] $SectionName, [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $IniFileContent, [Parameter(Mandatory, ParameterSetName = 'MoveAfter')] [string] $After, [Parameter(Mandatory, ParameterSetName = 'MoveBefore')] [string] $Before, [Parameter(Mandatory, ParameterSetName = 'MoveToStart')] [switch] $ToStart, [Parameter(Mandatory, ParameterSetName = 'MoveToEnd')] [switch] $ToEnd ) if (-not $IniFileContent.Contains($SectionName)) { Write-Warning ($script:Strings.SectionNotFound -f $SectionName) return } $content = $IniFileContent[$SectionName] $IniFileContent.Remove($SectionName) switch ($PSCmdlet.ParameterSetName) { 'MoveAfter' { if (-not $IniFileContent.Contains($After)) { Write-Warning ($script:Strings.RefSectionNotFound -f $After) $IniFileContent[$SectionName] = $content return } $index = $IniFileContent.Keys.IndexOf($After) + 1 $IniFileContent.Insert($index, $SectionName, $content) Write-Verbose ($script:Strings.SectionMovedAfter -f $SectionName, $After, $index) } 'MoveBefore' { if (-not $IniFileContent.Contains($Before)) { Write-Warning ($script:Strings.RefSectionNotFound -f $Before) $IniFileContent[$SectionName] = $content return } $index = $IniFileContent.Keys.IndexOf($Before) $IniFileContent.Insert($index, $SectionName, $content) Write-Verbose ($script:Strings.SectionMovedBefore -f $SectionName, $Before, $index) } 'MoveToStart' { $IniFileContent.Insert(0, $SectionName, $content) Write-Verbose ($script:Strings.SectionMovedToStart -f $SectionName) } 'MoveToEnd' { $IniFileContent[$SectionName] = $content Write-Verbose ($script:Strings.SectionMovedToEnd -f $SectionName) } } } # ───────────────────────────────────────────────────────────────────────────── # Move-IniFileValues # ───────────────────────────────────────────────────────────────────────────── function Move-IniFileValues { <# .ExternalHelp FITS.IniFileUtils-Help.xml #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory)] [string] $SourceSection, [Parameter(Mandatory)] [string] $DestinationSection, [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $IniFileContent, [string[]] $Keys, [switch] $Copy, [switch] $Overwrite ) if (-not $IniFileContent.Contains($SourceSection)) { Write-Warning ($script:Strings.SourceSectionNotFound -f $SourceSection) return } if (-not $IniFileContent.Contains($DestinationSection)) { Write-Warning ($script:Strings.DestSectionNotFound -f $DestinationSection) return } $targetKeys = if ($Keys) { $Keys } else { [string[]]($IniFileContent[$SourceSection].Keys) } $action = if ($Copy) { $script:Strings.ActionCopy } else { $script:Strings.ActionMove } foreach ($keyName in $targetKeys) { if (-not $IniFileContent[$SourceSection].Contains($keyName)) { Write-Warning ($script:Strings.KeyNotFound -f $keyName, $SourceSection) continue } if ($IniFileContent[$DestinationSection].Contains($keyName) -and -not $Overwrite) { Write-Warning ($script:Strings.KeyAlreadyExists -f $keyName, $DestinationSection) continue } if ($PSCmdlet.ShouldProcess("$SourceSection\$keyName -> $DestinationSection", $action)) { $IniFileContent[$DestinationSection][$keyName] = $IniFileContent[$SourceSection][$keyName] if (-not $Copy) { $IniFileContent[$SourceSection].Remove($keyName) } Write-Verbose ($script:Strings.KeyTransferred -f $action, $keyName, $SourceSection, $DestinationSection) } } } |