DSCResources/cIniFile/cIniFile.psm1
Enum Ensure{ Absent Present } Enum Encoding { Default utf8 utf8NoBOM utf8BOM utf32 unicode bigendianunicode ascii } function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $false)] [ValidateSet("Present", "Absent")] [System.String] $Ensure = 'Present', [parameter(Mandatory = $true)] [System.String] $Path, [parameter(Mandatory = $true)] [AllowEmptyString()] [System.String] $Key, [parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $Value = '', [parameter(Mandatory = $true)] [AllowEmptyString()] [System.String] $Section = '_ROOT_', [Parameter(Mandatory = $false)] [Encoding] $Encoding = 'utf8BOM', [Parameter(Mandatory = $false)] [ValidateSet('CRLF', 'LF')] [string] $NewLine = 'CRLF' ) if (-not $Section) {$Section = '_ROOT_'} [string]$tmpValue = '' # check file exists if (-not (Test-Path $Path -PathType Leaf)) { Write-Verbose ('File "{0}" not found.' -f $Path) $Ensure = [Ensure]::Absent } else { #Load ini file $Ini = Get-IniFile -Path $Path -Encoding $Encoding # if $key is empty, only check section if (-not $Key) { if ($Ini.$Section) { Write-Verbose ('Desired Section found ([{0}])' -f $Section) $Ensure = [Ensure]::Present } else { Write-Verbose ('Desired Section NOT found ([{0}])' -f $Section) $Ensure = [Ensure]::Absent } } # check section and key exists elseif ($Ini.$Section.Contains($Key)) { # check value Write-Verbose ('Current KVP (Key:"{0}"; Value:"{1}"; Section:"{2}")' -f $Key, $tmpValue, $Section) $Ensure = [Ensure]::Present $tmpValue = $Ini.$Section.$Key } else { Write-Verbose ('Desired Key or Section not found.') $Ensure = [Ensure]::Absent } } $returnValue = @{ Ensure = $Ensure Path = $Path Key = $Key Value = $tmpValue Section = $PSBoundParameters.Section } $returnValue } # end of Get-TargetResource function Set-TargetResource { param ( [parameter(Mandatory = $false)] [ValidateSet("Present", "Absent")] [System.String] $Ensure = 'Present', [parameter(Mandatory = $true)] [System.String] $Path, [parameter(Mandatory = $true)] [AllowEmptyString()] [System.String] $Key, [parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $Value = '', [parameter(Mandatory = $true)] [AllowEmptyString()] [System.String] $Section = '_ROOT_', [Parameter(Mandatory = $false)] [Encoding] $Encoding = 'utf8BOM', [Parameter(Mandatory = $false)] [ValidateSet('CRLF', 'LF')] [string] $NewLine = 'CRLF' ) $PSEncoder = Get-PSEncoding -Encoding $Encoding if (-not $Section) {$Section = '_ROOT_'} # Ensure = "Absent" if ($Ensure -eq [Ensure]::Absent) { if (Test-Path $Path) { Write-Verbose ("Remove Key:{0}; Section:{1} from '{2}'" -f $Key, $Section, $Path) $content = Get-IniFile -Path $Path -Encoding $Encoding | Remove-IniKey -Key $Key -Section $Section -PassThru | Out-IniString #Output Ini file if (('utf8', 'utf8NoBOM') -eq $Encoding) { $content | Out-String | Convert-NewLine -NewLine $NewLine | ForEach-Object { [System.Text.Encoding]::UTF8.GetBytes($_) } | Set-Content -Path $Path -Encoding Byte -NoNewline -Force } else { $content | Out-String | Convert-NewLine -NewLine $NewLine | Set-Content -Path $Path -Encoding $PSEncoder -NoNewline -Force } } } else { # Ensure = "Present" $Ini = [ordered]@{} if (Test-Path $Path) { $Ini = Get-IniFile -Path $Path -Encoding $Encoding } else { Write-Verbose ("Create new file '{0}'" -f $Path) New-Item $Path -ItemType File -Force } $content = $Ini | Set-IniKey -Key $Key -Value $Value -Section $Section -PassThru | Out-IniString #Output Ini file if (('utf8', 'utf8NoBOM') -eq $Encoding) { $content | Out-String | Convert-NewLine -NewLine $NewLine | ForEach-Object { [System.Text.Encoding]::UTF8.GetBytes($_) } | Set-Content -Path $Path -Encoding Byte -NoNewline -Force } else { $content | Out-String | Convert-NewLine -NewLine $NewLine | Set-Content -Path $Path -Encoding $PSEncoder -NoNewline -Force } } } # end of Set-TargetResource function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [parameter(Mandatory = $false)] [ValidateSet("Present", "Absent")] [System.String] $Ensure = 'Present', [parameter(Mandatory = $true)] [System.String] $Path, [parameter(Mandatory = $true)] [AllowEmptyString()] [System.String] $Key, [parameter(Mandatory = $false)] [AllowEmptyString()] [System.String] $Value = '', [parameter(Mandatory = $true)] [AllowEmptyString()] [System.String] $Section = '_ROOT_', [Parameter(Mandatory = $false)] [Encoding] $Encoding = 'utf8BOM', [Parameter(Mandatory = $false)] [ValidateSet('CRLF', 'LF')] [string] $NewLine = 'CRLF' ) if (-not $Section) {$Section = '_ROOT_'} $Ret = ($Ensure -eq [Ensure]::Present) if (-not (Test-Path -Path $Path -PathType Leaf)) { $Ret = !$Ret } else { $ini = Get-IniFile -Path $Path -Encoding $Encoding if ($ini.$Section) { # if $key is empty, only check whether section is exist or not if (-not $Key) { $Ret = $Ret } elseif ($ini.$Section.Contains($Key)) { if ($Value -ceq $ini.$Section.$Key) { $Ret = $Ret } else { $Ret = $false } } else { $Ret = !$Ret } } else { $Ret = !$Ret } } if ($Ret) { Write-Verbose ('Test Passed. Nothing needs to do') } else { Write-Verbose "Test NOT Passed." } return $Ret } # end of Test-TargetResource function Get-IniFile { [CmdletBinding()] param ( # Set Target full path to INI [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [validateScript( {Test-Path $_})] [Alias('File')] [string] $Path, # specify file encoding [Parameter(Mandatory = $false)] [Encoding] $Encoding = 'utf8BOM' ) process { # Write-Verbose ("Loading file from {0}" -f $Path) $PSEncoder = Get-PSEncoding -Encoding $Encoding $Content = Get-Content -Path $Path -Encoding $PSEncoder $CurrentSection = '_ROOT_' [System.Collections.Specialized.OrderedDictionary]$IniHash = [ordered]@{} $IniHash.Add($CurrentSection, [ordered]@{}) foreach ($line in $Content) { $line = $line.Trim() if ($line -match '^;') { # Write-Verbose ("Comment") $line = ($line.split(';')[0]).Trim() } if ($line -match '^\[(.+)\]') { # Section $CurrentSection = $Matches[1] if (-not $IniHash.Contains($CurrentSection)) { # Write-Verbose ('Add Section. Section: {0}' -f $Matches[1]) $IniHash.Add($CurrentSection, [ordered]@{}) } } elseif ($line -match '=') { #KeyValuePair $idx = $line.IndexOf('=') [string]$key = $line.Substring(0, $idx) [string]$value = $line.Substring($idx + 1) # Write-Verbose ('Add KVP. Key: {0}, Value: {1}, Section: {2}' -f $key,$value,$CurrentSection) $IniHash.$CurrentSection.$key = $value } } $IniHash } } function Out-IniString { [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [System.Collections.Specialized.OrderedDictionary] $InputObject ) Process { [string[]]$IniString = @() if ($InputObject.Contains('_ROOT_')) { if ($InputObject.'_ROOT_' -as [hashtable]) { $private:Keys = $InputObject.'_ROOT_' $Keys.Keys.ForEach( { $IniString += ('{0}={1}' -f $_, $Keys.$_) }) } } foreach ($Section in $InputObject.keys) { if (-not ($Section -eq '_ROOT_')) { if ($InputObject.$Section -as [hashtable]) { $IniString += ('[{0}]' -f $Section) $private:Keys = $InputObject.$Section $Keys.Keys.ForEach( { $IniString += ('{0}={1}' -f $_, $Keys.$_) }) } } } $IniString } } function Set-IniKey { [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [System.Collections.Specialized.OrderedDictionary] $InputObject, [Parameter(Mandatory)] [AllowEmptyString()] [string]$Key, [Parameter()] [AllowEmptyString()] [string]$Value = '', [Parameter()] [string]$Section = '_ROOT_', [switch]$PassThru ) Process { if ($InputObject.Contains($Section)) { if ($Key) { if ($InputObject.$Section.Contains($Key)) { Write-Verbose ("Update value. Key:'{0}'; Value:'{1}'; Section:'{2}'" -f $key, $Value, $Section) $InputObject.$Section.$Key = $Value } else { Write-Verbose ("Set value. Key:'{0}'; Value:'{1}'; Section:'{2}'" -f $key, $Value, $Section) $InputObject.$Section.Add($Key, $Value) } } } else { $InputObject.Add($Section, [System.Collections.Specialized.OrderedDictionary]@{}) if ($Key) { Write-Verbose ("Set value. Key:'{0}'; Value:'{1}'; Section:'{2}'" -f $key, $Value, $Section) $InputObject.$Section.Add($Key, $Value) } } if ($PassThru) { $InputObject } } } function Remove-IniKey { [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory, ValueFromPipeline)] [System.Collections.Specialized.OrderedDictionary] $InputObject, [parameter(Mandatory)] [AllowEmptyString()] [string]$Key, [parameter()] [string]$Section = '_ROOT_', [switch]$PassThru ) Process { if ($InputObject.Contains($Section)) { if ($Key) { if ($InputObject.$Section.Contains($Key)) { $InputObject.$Section.Remove($key) # when all key is removed, also remove section if ($InputObject.$Section.Count -le 0) { $InputObject.Remove($Section) } } } # if key is empty, remove section and all of child keys else { $InputObject.Remove($Section) } } if ($PassThru) { $InputObject } } } function Convert-NewLine { [OutputType([string])] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline)] [string] $InputObject, [Parameter(Position = 1)] [ValidateSet('CRLF', 'LF')] [string] $NewLine = 'CRLF' ) if ($NewLine -eq 'LF') { $InputObject.Replace("`r`n", "`n") } else { $InputObject -replace "[^\r]\n", "`r`n" } } function Get-PSEncoding { [OutputType([string])] param( [Parameter(Mandatory, Position = 0)] [Encoding] $Encoding ) switch -wildcard ($Encoding) { 'utf8*' { 'utf8' break } Default { $_.toString() } } } Export-ModuleMember -Function *-TargetResource |