2

I've been working on a script which will take IPs/DNSes and create a 2-column output. While it's working inside of powershell, as soon as I try to export to CSV it return system.object. How can I fix it? Here's my code:

function Resolve-IPDNS {
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string[]]$IPDNS,

    [string]$DNSServer = "1.1.1.1"

)#param

BEGIN {}

Process {
    foreach ($addrs in $IPDNS ) {

        #Resolve IP or DNS
        IF ($addrs -as [ipaddress]) {
        $resolve_params = @{'Name' = $addrs
            'Server' = $DNSServer
            'Type' = 'PTR'}
        } ELSE {
            $resolve_params = @{'Name' = $addrs
            'Server' = $DNSServer
            'Type' = 'A'}
        }
        $resolve = Resolve-DnsName @resolve_params


        #Create props
        IF ($addrs -as [ipaddress]) {
            $props = @{'IP' = $resolve.Name
                'DNS' = $resolve.NameHost}
        } ELSE {
                $props = @{'IP' = $resolve.IPAddress
                    'DNS' = $resolve.Name}

            }#IF

            #Output data
            $obj = New-Object -TypeName psobject -Property $props
            Write-Output $obj

        }#foreach

    }#Process
    END {}
}#function

Resolve-IPDNS

So if try to provide bbc.com I get result:

IP                                                            DNS
--                                                            ---
{151.101.0.81, 151.101.64.81, 151.101.128.81, 151.101.192.81} {bbc.com, bbc.com, bbc.com, bbc.com}

Is there a way to expand/unwrap while keeping the object-way?

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Generally, you need `Export-CliXml` / `Import-CliXml` rather than CSV format in order to _approximate_ preservation of the original types after serialization / deserialization. It's unclear what your specific problem is; please update your question directly to clarify. – mklement0 Apr 22 '18 at 21:55
  • Generally, strive for providing an [MCVE (Minimal, Complete, and Verifiable Example)](http://stackoverflow.com/help/mcve). – mklement0 Apr 22 '18 at 22:20

2 Answers2

3

Make sure you iterate over each result from Resolve-DnsName:

function Resolve-IPDNS {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]$IPDNS,

        [string]$DNSServer = "1.1.1.1"

    )

    Process {
        foreach($addrs in $IPDNS) {
            if($addrs -as [ipaddress]) {
                $resolve_params = @{
                    'Name'   = $addrs
                    'Server' = $DNSServer
                    'Type'   = 'PTR'
                }
            } 
            else {
                $resolve_params = @{
                    'Name'   = $addrs
                    'Server' = $DNSServer
                    'Type'   = 'A'
                }
            }

            foreach($resolve in Resolve-DnsName @resolve_params) {
                if($addrs -as [ipaddress]) {
                    $props = @{
                        'IP'  = $resolve.Name
                        'DNS' = $resolve.NameHost
                    }
                }
                else {
                    $props = @{
                        'IP'  = $resolve.IPAddress
                        'DNS' = $resolve.Name
                    }
                }

                #Output data
                $obj = New-Object -TypeName psobject -Property $props
                Write-Output $obj
            }
        }
    }
}
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • As yeah, this is a good approach too. It doesn't keep the number of rows the same as the number of items you passed in, but that likely isn't important anyway. – briantist Apr 22 '18 at 23:26
0

The problem here is that a name resolution can return multiple results. This is exactly what's happening with your example, bbc.com.

Your code doesn't do anything special with the result so that "column" (property really) of the object is itself an array. You can see this by the result your see on your screen; it's rendered as a comma separated list surrounded by { }.

When you go to export to CSV, each property has its .ToString() method called, which on a standard PowerShell array, will give you the exact result of System.Object[], which you can see as follows:

@(1,2,3).ToString()

So, you need to make a choice:

  • store the array in the object as a pre-rendered string as you want it to appear in a CSV (thus losing the original array type)
  • keep the array as storage, do your string rendering before exporting
  • add a helper method to your object that does the conversion or export

The -join operator will do nicely here since it will work even if the value is not an array.

For the first option, you can handle it easily on assignment:

@{
    'IP' = $resolve.Name -join '+'
    'DNS' = $resolve.NameHost -join '+'
}

For the second option, change your values on the fly in the pipeline:

Resolve-IPDNS -IPDNS 'bbc.com' | 
    ForEach-Object -Process {
        New-Object -TypeName PSObject -Property @{
            'IP' = $_.IP -join '+'
            'DNS' = $_.DNS -join '+'
        }
    } |
    Export-Csv MyCsv.csv -NoTypeInformation

For the third option, consider this:

$obj = New-Object -TypeName psobject -Property $props |
    Add-Member -MemberType ScriptMethod -Name JoinedClone -Value {
        New-Object -TypeName PSObject -Property @{ param([String]$JoinString
            'IP' = $this.IP -join $JoinString
            'DNS' = $this.DNS -join $JoinString
        }
    }

Then, you can do something like:

(Resolve-IPDNS -IPDNS 'bbc.com').JoinedClone() | Export-Csv MyCsv.csv -NoTypeInformation
briantist
  • 45,546
  • 6
  • 82
  • 127