TypeHelper.psm1

function ConvertTo-String
{
  [CmdletBinding()]
  param
  (
        [Parameter(Mandatory=$true)]
        [ValidateScript({
            If ('Hashtable','OrderedDictionary','AXNodeConfiguration','PSBoundParametersDictionary' -contains $_.GetType().Name)
            {
                $true
            }
            else
            {
                throw "Supported InputTypes are 'Hashtable' and 'OrderedDictionary'"
            }
        })]
        $InputObject,

        [Parameter(Mandatory=$false)]
        [switch]$DoNotFormat
    )
  
    Begin
    {
        function priv_Escape-SpecialChars
        {  
            param
            (
                [Parameter(Mandatory=$true, Position = 0)]
                [AllowEmptyString()]
                [string]$InputObject
            )

            if([string]::IsNullOrEmpty($InputObject))
            {
                return ""
            }
            else
            {
                [string]$ParsedText = $InputObject

                if($ParsedText.ToCharArray() -icontains "'")
                {
                    $ParsedText = $ParsedText -replace "'","''"
                }

                return $ParsedText
            }
        }
    }

    Process
    {
        $sb = new-object System.Text.StringBuilder

        $null = $sb.AppendLine('@{')

        foreach ($key in $InputObject.Keys)
        {
            if ($InputObject[$key])
            {
                switch ($inputObject[$key].GetType().Name)
                {
                    ### ScriptBlocks
                    'ScriptBlock' {
                        $null = $sb.AppendLine("$key = `{$($inputObject[$key].ToString())`}")
                        break
                    }

                    ### Strings and Enums
                    { @('String','ActionPreference') -contains $_ }  { 
                        [string]$itemText = "{0} = '{1}'" -f "$key", $(priv_Escape-SpecialChars -InputObject $inputObject[$key])
                        $null = $sb.AppendLine($itemText)
                        break
                    }

                    ### String Arrays
                    'String[]' {
                        [string]$itemText = "{0} = @({1})" -f "$key", "$($($inputObject[$key] | foreach { "'$(priv_Escape-SpecialChars -InputObject $_)'" }) -join ", ")"
                        $null = $sb.AppendLine($itemText)
                        break
                    }

                    ### Numerics
                    { ($_ -ilike '*int*') -or (@('single','double','decimal','SByte','Byte') -icontains $_) } {
                        [string]$itemText = "{0} = {1}" -f "$key", $($inputObject[$key]).ToString()
                        $null = $sb.AppendLine($itemText)
                        break
                    }

                    ### Nested Hashtables (recursive call)
                    {'Hashtable','OrderedDictionary','PSBoundParametersDictionary' -contains $_}  { 
                        [string]$itemText = "{0} = {1}" -f "$key", $(ConvertTo-String -InputObject $inputObject[$key] -DoNotFormat)
                        $null = $sb.AppendLine($itemText) 
                        break
                    }

                    ### Nested Hashtable Arrays (recursive call)
                    {'Hashtable[]','OrderedDictionary[]','PSBoundParametersDictionary[]' -contains $_}  { 
                        $NewLineStr = [Environment]::NewLine
                        $JoinSeparator = ",$NewLineStr"
                        [string]$itemText = "{0} = @($NewLineStr{1}$NewLineStr)" -f "$key", "$($($inputObject[$key] | foreach { ConvertTo-String -InputObject $_ -DoNotFormat }) -join $JoinSeparator)"
                        $null = $sb.AppendLine($itemText)
                        break
                    }

                    ### Booleans and Switches
                    { @('Boolean','SwitchParameter') -contains $_ } {
                        [string]$itemText = '{0} = ${1}' -f "$key", $($inputObject[$key].ToString())
                        $null = $sb.AppendLine($itemText)
                        break
                    }

                    ### PSCustomObject (NoteProperties only)
                    'PSCustomObject' {
                        # Convert to hashtable
                        $propHash = @{}
                        foreach ($prop in $inputObject[$key].PSObject.Properties)
                        {
                            $propHash[$prop.Name] = $prop.Value
                        }

                        [string]$itemText = '{0} = $([PSCustomObject] {1})' -f "$key", $(ConvertTo-String -InputObject $propHash -DoNotFormat)
                        $null = $sb.AppendLine($itemText) 
                        break
                    }

                    ### DateTime
                    'DateTime' {
                        [string]$itemText = "{0} = '{1}'" -f "$key", $($inputObject[$key].ToUniversalTime().ToString("dd.MM.yyyy HH.mm:ss UTC", [CultureInfo]::InvariantCulture))
                        $null = $sb.AppendLine($itemText) 
                        break
                    }

                    ### Object[] that contains only Hashtables
                    {($_ -eq 'object[]') -and ($inputObject[$key].Count -eq ($inputObject[$key] | Where-Object {$_.GetType().Name -eq 'HashTable'} | measure | select -ExpandProperty count))} {

                        $NewLineStr = [Environment]::NewLine
                        $JoinSeparator = ",$NewLineStr"
                        [string]$itemText = "{0} = @($NewLineStr{1}$NewLineStr)" -f "$key", "$($($inputObject[$key] | foreach { ConvertTo-String -InputObject $_ -DoNotFormat }) -join $JoinSeparator)"
                        $null = $sb.AppendLine($itemText)
                        break

                    }

                    Default {
                        Write-Warning "Serializing not supported key: $key that contains: $_"
                        [string]$itemText = '{0} = {1}' -f "$key", $($inputObject[$key].ToString())
                        $null = $sb.AppendLine($itemText)
                    }
                }
            }
            else
            {
                $null = $sb.AppendLine('{0} = $null' -f "$key")
            }
        }

        $null =  $sb.AppendLine('}')
    
        $result = $sb.ToString()

        if($DoNotFormat.IsPresent)
        {
            $result.Trim([environment]::NewLine)
        }
        else
        {
            ConvertTo-TabifiedString -ScriptText $result
        }
    }

    End
    {

    }
}

function ConvertTo-Hashtable
{

    param
    (
        [ValidateScript({
        $TempParam = $_
        switch ($TempParam.GetType().Fullname)
        {
            'System.String' {
                try
                {
                    $obj = ConvertFrom-Json -InputObject $TempParam -ErrorAction Stop
                    $Script:InputObjectData = $obj.psobject.Properties
                }
                catch
                {
                    throw "InputObject is not a valid json string"
                }
                break
            }
            default {
                $Script:InputObjectData = $TempParam.psobject.Properties
            }
        }
        $true
    })]
        $InputObject
    )

    begin
    {
        $DepthThreshold = 32

        function Get-IOProperty
        {
            param
            (
                [Parameter(Mandatory=$true)]
                [System.Management.Automation.PSPropertyInfo[]]$Property,

                [Parameter(Mandatory=$true)]
                [int]$CurrentDepth
            )
            
            #Increse and chech Depth
            $CurrentDepth++
            if ($Function:Depth -ge $DepthThreshold)
            {
                Write-Error -Message "Converting to Hashtable reached Depth Threshold of 32 on $($Property.Name -join ',')" -ErrorAction Stop
            }

            $Ht = [hashtable]@{}
            foreach ($Prop in $Property)
            {
                if ($Prop.Value)
                {
                    switch ($Prop.TypeNameOfValue)
                    {
                        'System.String' {
                            $ht.Add($Prop.Name,$Prop.Value)
                            break
                        }
                        'System.Boolean' {
                            $ht.Add($Prop.Name,$Prop.Value)
                            break
                        }
                        'System.DateTime' {
                            $ht.Add($Prop.Name,$Prop.Value.ToString())
                            break
                        }
                        {$_ -ilike '*int*'} {
                            $ht.Add($Prop.Name,$Prop.Value)
                            break
                        }
                        default {
                            $ht.Add($Prop.Name,(Get-IOProperty -Property $Prop.Value.psobject.Properties -CurrentDepth $CurrentDepth))
                        }
                    }
                }
                else
                {
                    $ht.Add($Prop.Name,$null)
                }
            }
            $Ht
        }
    }
  
    process
    {
        $CurrentDepth = 0
        Get-IOProperty -Property $InputObjectData -CurrentDepth $CurrentDepth
    }
  
    end
    {
    }
}

function ConvertTo-TabifiedString
{
    [CmdletBinding()]
    Param
    (
        $ScriptText
    ) 
    
    $CurrentLevel = 0
    $ParseError = $null
    $Tokens = $null
    $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptText, [ref]$Tokens, [ref]$ParseError) 
    
    if($ParseError) { 
    $ParseError | Write-Error
    throw 'The parser will not work properly with errors in the script, please modify based on the above errors and retry.'
    }
    
    for($t = $Tokens.Count -2; $t -ge 1; $t--) {
        
    $Token = $Tokens[$t]
    $NextToken = $Tokens[$t-1]
        
    if ($token.Kind -match '(L|At)Curly') { 
        $CurrentLevel-- 
    }  
        
    if ($NextToken.Kind -eq 'NewLine' ) {
        # Grab Placeholders for the Space Between the New Line and the next token.
        $RemoveStart = $NextToken.Extent.EndOffset  
        $RemoveEnd = $Token.Extent.StartOffset - $RemoveStart
        $tabText = "`t" * $CurrentLevel 
        $ScriptText = $ScriptText.Remove($RemoveStart,$RemoveEnd).Insert($RemoveStart,$tabText)
    }
        
    if ($token.Kind -eq 'RCurly') { 
        $CurrentLevel++ 
    }     
    }

    $ScriptText
}

function Resolve-ObjectProperty
{
    [CmdletBinding()]
    [OutputType([object])]
    param
    (
        #InputObject
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [object[]]$InputObject,

        #PropertyName
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [string]$PropertyName,

        #PropertyValueReference
        [Parameter(Mandatory=$true,ParameterSetName='NoRemoting_Default')]
        [hashtable]$PropertyValueReference
    )
    
    Begin
    {
          
    }

    Process
    {
        foreach ($object in $InputObject)
        {
            if ($object.psobject.Properties.Name -contains $PropertyName)
            {
                if ($PropertyValueReference.ContainsKey(($object.$PropertyName)))
                {
                    $object.$PropertyName = $PropertyValueReference[$object.$PropertyName]            
                }
            }
        }
        $InputObject
    }

    End
    {

    }
}

function Import-PSDataFile
{
    [CmdletBinding()]
    Param 
    (
        [Parameter(Mandatory)]
        [Microsoft.PowerShell.DesiredStateConfiguration.ArgumentToConfigurationDataTransformation()]
        [object] $FilePath    
    )
    return $FilePath
}