lipkau / PsIni

Work with INI files in PowerShell using hashtables
http://lipkau.github.io/PsIni
MIT License
151 stars 50 forks source link

Submission: ConvertFrom-Ini and ConvertTo-Ini #62

Open ghost opened 3 years ago

ghost commented 3 years ago

I would make a pull request, but I'm not that well versed in working with git/github. Nevermind the fact there's a version mismatch with the latest tag and what's in the github psd1 file: #61

These are based on Get-IniContent and Out-IniFile functions from PsIni version 3.1.2 pulled from the public PSGallery. I actually need to work with a web sourced ini file, and prefer not to download it to the filesystem if I don't have to. The following functions do exactly what I need them to do and behave consistently with their reference function.

I know you have something you're working on for 4.0, but I'm a little impatient and will be using these submissions for my own needs in the meantime until you are comfortable releasing 4.0 to the gallery.

#example script
Import-Module PsIni
Import-Module HPCMSL
$SoftPaq = Get-SoftPaqList -Category DriverPack
$MetaData = Invoke-WebRequest -Uri "https://$($SoftPaq.MetaData)" | Select-Object -ExpandProperty Content
$IniData = $MetaData | ConvertFrom-Ini
$keys = $IniData.'System Information'.Keys.split()
$count = ($keys.count / 2)
$SystemInformation = 1..$count | Foreach-Object {
     if ($_.length -eq 1) {
          $index = "0$_"
     } else {
          $index = $_.ToString()
     }
     $System = $keys | Where-Object {$_ -like "*$Index"}
     [PSCustomObject]@{
          ProductCode = $IniData.'System Information'[$System[0]].split('x')[1]
          Models = $IniData.'System Information'[$System[1]].Split(',')
     }
}
Write-Output $SystemInformation

Example Output

ProductCode Models
----------- ------
870B        {HP ELITEDESK 800 G6 SMALL FORM FACTOR, HP ELITEDESK 800 G6 TOWER, HP ELITEDESK 880 G6 TOWER, HP Z1 G6 Entry Tower}
870C        {HP ELITEDESK 800 G6 SMALL FORM FACTOR, HP ELITEDESK 800 G6 TOWER, HP ELITEDESK 880 G6 TOWER, HP Z1 G6 Entry Tower}
8712        {HP PRODESK 600 G6 MICROTOWER}
8713        {HP PRODESK 600 G6 I MICROTOWER, HP PRODESK 680 G6 I MICROTOWER}
8717        {HP PRODESK 400 G7 MICROTOWER}
8718        {HP PRODESK 480 G7 I MICROTOWER}
Function ConvertFrom-Ini {
    <#
    .Synopsis
        Converts the content of a string (such as from Get-Content)

    .Description
        Converts the content of an INI formatted string object and returns it as a hashtable

    .Notes
        Author      : Oliver Lipkau <oliver@lipkau.net>
        Blog            : http://oliver.lipkau.net/blog/
    Source      : https://github.com/lipkau/PsIni
                      http://gallery.technet.microsoft.com/scriptcenter/ea40c1ef-c856-434b-b8fb-ebd7a76e8d91

        #Requires -Version 2.0

    .Inputs
        System.String

    .Outputs
        System.Collections.Specialized.OrderedDictionary

    .Example
        $IniFileContent = Get-Content "C:\myinifile.ini" | ConvertFrom-Ini
        -----------
        Description
        Saves the content of the c:\myinifile.ini in a hashtable called $FileContent

    .Example
        $IniContent = (Invoke-WebRequest -Uri $uri).Content | ConvertFrom-Ini
        -----------
        Description
        Gets the content of a web-sourced ini file passed through the pipe into a hashtable called $IniContent

    .Example
        C:\PS>$FileContent = Get-Content "c:\settings.ini" | ConvertFrom-Ini
        C:\PS>$FileContent["Section"]["Key"]
        -----------
        Description
        Returns the key "Key" of the section "Section" from the C:\settings.ini file

    .Link
    Get-IniContent
        Out-IniFile
    ConvertTo-Ini
    #>

    [CmdletBinding()]
    [OutputType(
        [System.Collections.Specialized.OrderedDictionary]
    )]
    Param(
        # Specifies the path to the input file.
        [ValidateNotNullOrEmpty()]
        [Parameter( Mandatory = $true, ValueFromPipeline = $true )]
        [String]
        $InputObject,

        # Specify what characters should be describe a comment.
        # Lines starting with the characters provided will be rendered as comments.
        # Default: ";"
        [Char[]]
        $CommentChar = @(';','#','/'),

        # Remove lines determined to be comments from the resulting dictionary.
        [Switch]
        $IgnoreComments
    )

    Begin {
        Write-Debug "PsBoundParameters:"
        $PSBoundParameters.GetEnumerator() | ForEach-Object { Write-Debug $_ }
        if ($PSBoundParameters['Debug']) {
            $DebugPreference = 'Continue'
        }
        Write-Debug "DebugPreference: $DebugPreference"

        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"

        $commentRegex = "^\s*([$($CommentChar -join '')].*)$"
        $sectionRegex = "^\s*\[(.+)\]\s*$"
        $keyRegex     = "^\s*(.+?)\s*=\s*(['`"]?)(.*)\2\s*$"

        Write-Debug ("commentRegex is {0}." -f $commentRegex)
    }

    Process {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing [System.String]"

        $ini = New-Object System.Collections.Specialized.OrderedDictionary([System.StringComparer]::OrdinalIgnoreCase)
        #$ini = @{}

        $commentCount = 0
    foreach ($Line in $InputObject.Split([System.Environment]::NewLine)) {
        switch -regex ($Line) {
            $sectionRegex {
                # Section
                $section = $matches[1]
                Write-Verbose "$($MyInvocation.MyCommand.Name):: Adding section : $section"
                $ini[$section] = New-Object System.Collections.Specialized.OrderedDictionary([System.StringComparer]::OrdinalIgnoreCase)
                $CommentCount = 0
                continue
            }
            $commentRegex {
                # Comment
                if (!$IgnoreComments) {
                    if (!(test-path "variable:local:section")) {
                        $section = $script:NoSection
                        $ini[$section] = New-Object System.Collections.Specialized.OrderedDictionary([System.StringComparer]::OrdinalIgnoreCase)
                    }
                    $value = $matches[1]
                    $CommentCount++
                    Write-Debug ("Incremented CommentCount is now {0}." -f $CommentCount)
                    $name = "Comment" + $CommentCount
                    Write-Verbose "$($MyInvocation.MyCommand.Name):: Adding $name with value: $value"
                    $ini[$section][$name] = $value
                }
                else {
                    Write-Debug ("Ignoring comment {0}." -f $matches[1])
                }

                continue
            }
            $keyRegex {
                # Key
                if (!(test-path "variable:local:section")) {
                    $section = $script:NoSection
                    $ini[$section] = New-Object System.Collections.Specialized.OrderedDictionary([System.StringComparer]::OrdinalIgnoreCase)
                }
                $name, $value = $matches[1, 3]
                Write-Verbose "$($MyInvocation.MyCommand.Name):: Adding key $name with value: $value"
                if (-not $ini[$section][$name]) {
                    $ini[$section][$name] = $value
                }
                else {
                    if ($ini[$section][$name] -is [string]) {
                        $ini[$section][$name] = [System.Collections.ArrayList]::new()
                        $ini[$section][$name].Add($ini[$section][$name]) | Out-Null
                        $ini[$section][$name].Add($value) | Out-Null
                    }
                    else {
                        $ini[$section][$name].Add($value) | Out-Null
                    }
                }
                continue
            }
        }
    }
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing [System.String]"
        Write-Output $ini
    }

    End {
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"
    }
}

Set-Alias cfi ConvertFrom-Ini
Function ConvertTo-Ini {
    <#
    .Synopsis
        Write hash content to INI string object

    .Description
        Write hash content to INI string object

    .Notes
        Author      : Oliver Lipkau <oliver@lipkau.net>
        Blog        : http://oliver.lipkau.net/blog/
        Source      : https://github.com/lipkau/PsIni
                      http://gallery.technet.microsoft.com/scriptcenter/ea40c1ef-c856-434b-b8fb-ebd7a76e8d91

        #Requires -Version 2.0

    .Inputs
        System.String
        System.Collections.IDictionary

    .Outputs
        System.String

    .Example
        $IniVar | ConvertTo-Ini | Out-File "C:\myinifile.ini"
        -----------
        Description
        Saves the content of the $IniVar Hashtable to the INI File c:\myinifile.ini

    .Example
        $IniVar | ConvertTo-Ini | Out-File "C:\myinifile.ini" -Force
        -----------
        Description
        Saves the content of the $IniVar Hashtable to the INI File c:\myinifile.ini and overwrites the file if it is already present

    .Example
        $Category1 = @{“Key1”=”Value1”;”Key2”=”Value2”}
        $Category2 = @{“Key1”=”Value1”;”Key2”=”Value2”}
        $NewINIContent = @{“Category1”=$Category1;”Category2”=$Category2}
        ConvertTo-Ini -InputObject $NewINIContent | Out-File -FilePath "C:\MyNewFile.ini"
        -----------
        Description
        Creating a custom Hashtable and saving it to C:\MyNewFile.ini
    .Link
        Get-IniContent
    ConvertFrom-Ini
    #>

    [CmdletBinding()]
    [OutputType(
        [System.IO.FileSystemInfo]
    )]
    Param(
        # Specifies the Hashtable to be written to the file. Enter a variable that contains the objects or type a command or expression that gets the objects.
        [Parameter( Mandatory = $true, ValueFromPipeline = $true )]
        [System.Collections.IDictionary]
        $InputObject,

        # Adds spaces around the equal sign when writing the key = value
        [Switch]
        $Loose,

        # Writes the file as "pretty" as possible
        #
        # Adds an extra linebreak between Sections
        [Switch]
        $Pretty
    )

    Begin {
        Write-Debug "PsBoundParameters:"
        $PSBoundParameters.GetEnumerator() | ForEach-Object { Write-Debug $_ }
        if ($PSBoundParameters['Debug']) {
            $DebugPreference = 'Continue'
        }
        Write-Debug "DebugPreference: $DebugPreference"

        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"

        function Out-Keys {
            param(
                [ValidateNotNullOrEmpty()]
                [Parameter( Mandatory, ValueFromPipeline )]
                [System.Collections.IDictionary]
                $InputObject,

                [Parameter( Mandatory )]
                $Delimiter,

                [Parameter( Mandatory )]
                $MyInvocation
            )

            Process {
                if (!($InputObject.get_keys())) {
                    Write-Warning ("No data found in '{0}'." -f $FilePath)
                }
        $outKey = @()
                Foreach ($key in $InputObject.get_keys()) {
                    if ($key -match "^Comment\d+") {
                        Write-Verbose "$($MyInvocation.MyCommand.Name):: Writing comment: $key"
                        $outKey += "$($InputObject[$key])"
                    }
                    else {
                        Write-Verbose "$($MyInvocation.MyCommand.Name):: Writing key: $key"
                        $InputObject[$key] |
                            ForEach-Object { $outKey +=  "$key$delimiter$_" }
                    }
                }
        Write-Output $outKey
            }
        }

        $delimiter = '='
        if ($Loose) {
            $delimiter = ' = '
        }

    }

    Process {
        $extraLF = ""

    Write-Debug ("Creating Output Object")
        $output = @()

        Write-Verbose "$($MyInvocation.MyCommand.Name):: Appending to object: [System.Object]"
        foreach ($i in $InputObject.get_keys()) {
            if (!($InputObject[$i].GetType().GetInterface('IDictionary'))) {
                #Key value pair
                Write-Verbose "$($MyInvocation.MyCommand.Name):: Writing key: $i"
                $output += "$i$delimiter$($InputObject[$i])"
            }
            elseif ($i -eq $script:NoSection) {
                #Key value pair of NoSection
                $output += Out-Keys $InputObject[$i] `
                    -Delimiter $delimiter `
                    -MyInvocation $MyInvocation
            }
            else {
                #Sections
                Write-Verbose "$($MyInvocation.MyCommand.Name):: Writing Section: [$i]"

                # Only write section, if it is not a dummy ($script:NoSection)
                if ($i -ne $script:NoSection) { $output += "$extraLF[$i]"}
                if ($Pretty) {
                    $extraLF = "`r`n"
                }

                if ( $InputObject[$i].Count) {
                    $output += Out-Keys $InputObject[$i] `
                        -Delimiter $delimiter `
                        -MyInvocation $MyInvocation
                }

            }
        }
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Writing to object: [System.Object]"
    }

    End {
        Write-Output $output
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"
    }
}

Set-Alias cti ConvertTo-Ini