PoshCode Logo PowerShell Code Repository

ConvertFrom-Property by Joel Bennett 18 months ago (modification of post by Joel Bennett view diff)
diff | embed code: <script type="text/javascript" src="http://PoshCode.org/embed/2050"></script>download | new post

ConvertFrom-PropertyString 3.0 can convert ini files, property files, and other flat key-value data strings into PSObjects.

  1. <#
  2. .SYNOPSIS
  3.    Converts data from flat or single-level property files into PSObjects
  4. .DESCRIPTION
  5.    Converts delimited string data into objects
  6. .PARAMETER InputObject
  7.    The text to be parsed
  8. .PARAMETER FilePath
  9.    A file containing text to be parsed (so you can pipeline files to be processed)
  10. .PARAMETER ValueSeparator
  11.    The value separator string used between name=value pairs. Allows regular expressions.
  12.    Typical values are "=" or ":" or ";"
  13.    Defaults to "="
  14. .PARAMETER PropertySeparator
  15.    The property separator string used between sets of name=value pairs. Allows regular expressions.
  16.    Typical values are "`n" or "`n`n" or "\n\s*\n"
  17.    Defaults to "\n\s*\n?"
  18. .PARAMETER CountOfPropertiesPerRecord
  19.    Separate the input into groups of a certain number of properties.
  20.    If your input file has no specific record separator, you can usually match the first property by using a look-ahead expression *(See Example 2)*
  21.    However, if the properties aren't in the same order each time or regular expressions make you queasy, and each of your records have the same number of properties on each record, you can use this to separate them by count.  
  22. .PARAMETER RecordSeparator
  23.    The record separator string is used between records or sections in a text file.
  24.    Typical values are "\n\s*\n" or "\n\[(.*)\]\s*\n"
  25.    Defaults to "\n\[(.+)\]\s*\n" (the correct value for ini files).
  26.    
  27.    To support named sections or records, make sure to use a regular expression here that has a capture group defined.
  28. .PARAMETER AutomaticRecords
  29.    Supports guessing when a new record starts based on the repetition of a property name. You can use this whenever your input has multiple records and the properties are always in the same order.
  30. .PARAMETER SimpleOutput
  31.    Prevent outputting the PSName parameter which indicates the source of the object when pipelineing file names
  32. .EXAMPLE
  33.    ConvertFrom-PropertyString config.ini
  34.    
  35.    Reads in an ini file (which has key=value pairs), using the default settings
  36.  
  37.    .EXAMPLE
  38.    @"
  39.    ID:3468
  40.    Type:Developer
  41.    StartDate:1998-02-01
  42.    Code:SWENG3
  43.    Name:Baraka
  44.  
  45.    ID:11234
  46.    Type:Management
  47.    StartDate:2005-05-21
  48.    Code:MGR1
  49.    Name:Jax
  50.    "@ |ConvertFrom-PropertyString -sep ":" -RecordSeparator "\r\n\s*\r\n" | Format-Table
  51.  
  52.  
  53.    Code             StartDate       Name            ID              Type          
  54.    ----             ---------       ----            --              ----          
  55.    SWENG3           1998-02-01      Baraka          3468            Developer      
  56.    MGR1             2005-05-21      Jax             11234           Management    
  57.      
  58.    Reads records from a key:value string with records separated by blank lines.
  59.    NOTE that in this example you could also have used -AutomaticRecords or -Count 5 instead of specifying a RecordSeparator
  60. .EXAMPLE
  61.    @"
  62.    Name=Fred
  63.    Address=Street1
  64.    Number=123
  65.    Name=Janet
  66.    Address=Street2
  67.    Number=345
  68.    "@ | ConvertFrom-PropertyString -RecordSeparator "`n(?=Name=)"
  69.  
  70.    Reads records from a key=value string and uses a look-ahead record separator to start a new record whenever "Name=" is encountered
  71.    
  72.    NOTE that in this example you could have used -AutomaticRecords or -Count 3 instead of specifying a RecordSeparator
  73. .EXAMPLE
  74.    ConvertFrom-PropertyString data.txt -ValueSeparator ":"
  75.    
  76.    Reads in a property file which has key:value pairs
  77. .EXAMPLE
  78.    Get-Content data.txt -RecordSeparator "`r`n`r`n" | ConvertFrom-PropertyString -ValueSeparator ";"
  79.    
  80.    Reads in a property file with key;value pairs, and records separated by blank lines, and converts it to objects
  81. .EXAMPLE
  82.    ls *.data | ConvertFrom-PropertyString
  83.    
  84.    Reads in a set of *.data files which have an object per file defined with key:value pairs of properties, one-per line.
  85. .EXAMPLE
  86.    ConvertFrom-PropertyString data.txt -RecordSeparator "^;(.*?)\r*\n" -ValueSeparator ";"
  87.    
  88.    Reads in a property file with key:value pairs, and sections with a header that starts with the comment character ';'
  89.    
  90. .NOTES
  91.    3.0   2010 Aug 4 (This Version)
  92.          - Renamed most of the parameters because I couldn't tell which did what from the Syntax help
  93.          - Added a -AutomaticRecords switch which creates new output objects whenevr it encounters a duplicated property
  94.          - Added a -SimpleOutput swicth which prevents the output of the PSChildName property
  95.          - Added a -CountOfPropertiesPerRecord parameter which allows splitting input by count instead of regex or automatic
  96.    2.0   2010 July 9 http://poshcode.org/get/1956
  97.          - changes the output so that if there are multiple instances of the same key, we collect the values in an array
  98.    1.0   2010 June 15 http://poshcode.org/get/1915
  99.          - Initial release
  100.    
  101. #>
  102.  
  103. #function ConvertFrom-PropertyString {
  104. [CmdletBinding(DefaultParameterSetName="Data")]
  105. param(
  106.    [Parameter(Position=99, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Data")]
  107.    [Alias("Data","Content","IO")]
  108.    [string]$InputObject
  109. ,
  110.    [Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ParameterSetName="File")]
  111.    [Alias("PSPath")]
  112.    [string]$FilePath
  113. ,
  114.    [Parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false)]
  115.    [Alias("VS","Separator")]
  116.    [String]$ValueSeparator="\s*=\s*"
  117. ,
  118.    [Parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false)]
  119.    [Alias("PS","Delimiter")]
  120.    [String]$PropertySeparator='(?:\s*\n+\s*)+'
  121. ,
  122.    [Parameter(ValueFromPipeline=$false, ValueFromPipelineByPropertyName=$false)]
  123.    [Alias("RS")]
  124.    [String]$RecordSeparator='(?:\n|^)\[([^\]]+)\]\s*\n'
  125. ,
  126.    [Parameter(ParameterSetName="Data")]
  127.    [Alias("MultiRecords","MR","MultipleRecords","AR","AutoRecords")]
  128.    [Switch]$AutomaticRecords
  129. ,
  130.    [Parameter()]
  131.    [int]$CountOfPropertiesPerRecord
  132. ,
  133.    [Parameter()]
  134.    [Switch]$SimpleOutput
  135. )
  136. begin {
  137.    function new-output {
  138.       [CmdletBinding()]
  139.       param(
  140.          [Switch]$SimpleOutput
  141.       ,
  142.          [AllowNull()][AllowEmptyString()]
  143.          [String]$Key
  144.       ,
  145.          [AllowNull()][AllowEmptyString()]
  146.          $FilePath
  147.       )
  148.       end {
  149.          if(!$SimpleOutput -and ("" -ne $Key))  { @{"PSName"=$key} }
  150.          elseif(!$SimpleOutput -and $FilePath)  { @{"PSName"=((get-item $FilePath).PSChildName)} }
  151.          else                                   { @{} }
  152.       }
  153.    }
  154.  
  155.    function out-output {
  156.       [CmdletBinding()]
  157.       param([Hashtable]$output)
  158.       end {
  159.          ## If we made arrays out of single values, unwrap those
  160.          foreach($k in $Output.Keys | Where { $Output.$_.Count -eq 1 } ) {
  161.             $Output.$k = $Output.$k[0]
  162.          }
  163.          if($output.Count) {
  164.             New-Object PSObject -Property $output
  165.          }
  166.       }
  167.    }
  168.  
  169.    Write-Verbose "Setting up the regular expressions: `n`tRecord: '$RecordSeparator'  `n`tProperty: '$PropertySeparator'  `n`tValue: '$ValueSeparator'"
  170.    [Regex]$ReRecordSeparator   = New-Object Regex ([System.String]$RecordSeparator),   ([System.Text.RegularExpressions.RegexOptions]"Multiline,IgnoreCase,Compiled")
  171.    [Regex]$RePropertySeparator = New-Object Regex ([System.String]$PropertySeparator), ([System.Text.RegularExpressions.RegexOptions]"Multiline,IgnoreCase,Compiled")
  172.    [Regex]$ReValueSeparator    = New-Object Regex ([System.String]$ValueSeparator),    ([System.Text.RegularExpressions.RegexOptions]"Multiline,IgnoreCase,Compiled")
  173. }
  174. process {
  175.    ## some kind of PowerShell bug when expecting pipeline input:  
  176.    if(!"$ReRecordSeparator"){
  177.       Write-Verbose "Setting up the record regex in the PROCESS block: '$RecordSeparator'"
  178.       [Regex]$ReRecordSeparator   = New-Object Regex ([System.String]$RecordSeparator),   ([System.Text.RegularExpressions.RegexOptions]"Multiline,IgnoreCase,Compiled")
  179.    }
  180.    if(!"$RePropertySeparator"){
  181.       Write-Verbose "Setting up the property regex in the PROCESS block: '$PropertySeparator'"
  182.       [Regex]$RePropertySeparator = New-Object Regex ([System.String]$PropertySeparator), ([System.Text.RegularExpressions.RegexOptions]"Multiline,IgnoreCase,Compiled")
  183.    }
  184.    if(!"$ReValueSeparator") {  
  185.       Write-Verbose "Setting up the value regex in the PROCESS block: '$ValueSeparator'"
  186.       [Regex]$ReValueSeparator    = New-Object Regex ([System.String]$ValueSeparator),    ([System.Text.RegularExpressions.RegexOptions]"Multiline,IgnoreCase,Compiled")
  187.    }
  188.    Write-Verbose "ParameterSet: $($PSCmdlet.ParameterSetName)"
  189.    Write-Verbose "ValueSeparator: $($ReValueSeparator)"
  190.    $InputData = @{}
  191.    if($PSCmdlet.ParameterSetName -eq "File") {
  192.       $AutomaticRecords = $true
  193.       $InputObject = Get-Content $FilePath -Delimiter ([char]0)
  194.    }
  195.    
  196.    ## Separate RecordText with the RecordSeparator if the user asked us to:
  197.    if($PsBoundParameters.ContainsKey('RecordSeparator') -or $AutomaticRecords ) {
  198.       $Records = $ReRecordSeparator.Split( $InputObject ) | Where-Object { $_ }
  199.       Write-Verbose "There are $($ReRecordSeparator.GetGroupNumbers().Count) groups and $(@($Records).Count) records!"
  200.       if($ReRecordSeparator.GetGroupNumbers().Count -gt 1 -and @($Records).Count -gt 1) {
  201.          while($Records) {
  202.             $Key,$Value,$Records = $Records
  203.             Write-Verbose "RecordSeparator with grouping: $Key = $Value"
  204.             $InputData.$Key += @($Value)
  205.          }
  206.       } elseif(@($Records).Count -gt 1) {
  207.          $InputData."" = @($Records)
  208.          $InputObject = $Records
  209.       } else {
  210.          $InputObject = $Records
  211.       }
  212.    }
  213.    
  214.    ## Separate RecordText into properties and group them together by count if we were told a count
  215.    if($PsBoundParameters.ContainsKey('CountOfPropertiesPerRecord')) {  
  216.       $Properties = $RePropertySeparator.Split($InputObject)
  217.       Write-Verbose "Separating Records by Property count = $CountOfPropertiesPerRecord of $($Properties.Count)"
  218.       for($Index = 0; $Index -lt $Properties.Count; $Index += $CountOfPropertiesPerRecord) {
  219.          $InputData."" += @($Properties[($Index..($Index+$CountOfPropertiesPerRecord-1))] -Join ([char]0))
  220.          Write-Verbose "Record ($Index..) $($Index/$CountOfPropertiesPerRecord) = $(@($Properties[($Index..($Index+$CountOfPropertiesPerRecord-1))] -Join ([char]0)))"
  221.       }
  222.       ## We have to manually set the PropertySeparator because we can't generate text from your regex pattern to match your regex pattern
  223.       $SetPropertySeparator = $RePropertySeparator
  224.       [Regex]$RePropertySeparator = New-Object Regex ([System.String][char]0), ([System.Text.RegularExpressions.RegexOptions]"Multiline,IgnoreCase,Compiled")
  225.    }
  226.    if($InputData.Keys.Count -eq 0){
  227.       Write-Verbose "Keyless entry enabled!"
  228.       $InputData."" = @($InputObject)
  229.    }
  230.    
  231.    Write-Verbose "InputData: $($InputData.GetEnumerator() | ft -auto -wrap| out-string)"
  232.  
  233.    ## Process each Record
  234.    foreach($key in $InputData.Keys) { foreach($record in $InputData.$Key) {
  235.       Write-Verbose "Record($Key): $record"
  236.      
  237.       $output = new-output -SimpleOutput:$SimpleOutput -Key:$Key -FilePath:$FilePath
  238.      
  239.       foreach($Property in $RePropertySeparator.Split("$record")) {
  240.          [string[]]$data = $ReValueSeparator.split($Property,2) | foreach { $_.Trim() } | where { $_ }
  241.          Write-Verbose "Property: $Property --> $($data -join ': ')"
  242.          if($AutomaticRecords -and $Output.ContainsKey($Data[0])) {
  243.             out-output $output
  244.             $output = new-output -SimpleOutput:$SimpleOutput -Key:$Key -FilePath:$FilePath
  245.          }
  246.          switch($data.Count) {
  247.             1 { $output.($Data[0]) += @($null)    }
  248.             2 { $output.($Data[0]) += @($Data[1]) }
  249.          }
  250.       }
  251.       out-output $output
  252.      
  253.    }  }
  254.    ## Put this back in case there's more input
  255.    $RePropertySeparator = $SetPropertySeparator
  256. }
  257. #}

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me