Help Formatting Output of a Script

Hello Everyone,

I’m trying to find a way to get a full line for each item for this script. I am pulling all of the users from corporate headquarters and getting mobile device information for any device they have connected with in the past 30 days. It works great, but the security folks want to be able to sort the information better. So instead of only having the Identifiable information for the user once, they would like each device to basically be it’s own line.

 

Here is a screenshot of how the data is currently.

Here is the code that I’m using:

 

#Define Variables
$Data = New-Object System.Collections.ArrayList
#$Data = Get-EXOMailbox -ResultSize Unlimited | Sort-Object DisplayName
$Data = Import-Csv 'C:\Users\rmartin\Scripts\corp_mobile_upns.csv'

$groupamount = 25
$Number = [math]::ceiling($data.count / $groupamount)
$Item = New-Object System.Collections.ArrayList
$Count = 0
$Inc = 0
$b = 0
#End Variable Definitions

#Count up from 0 to ($Data.Count / 100) by 100 users a time to get all employees#
For ($count=0; $count -le $Number; $Count++){
    
    $ID = New-Object System.Collections.ArrayList
    $Users = New-Object System.Collections.ArrayList
    $Rollup = New-Object System.Collections.ArrayList
    $i = 0

    $Users = $Data | Select-Object -Skip ($Inc+0) -First $groupamount

    ForEach ($User in $Users) {
        $ID = Get-EXOMobileDeviceStatistics -UserPrincipalName $User.UserPrincipalName | Where-Object LastSuccessSync -ge (Get-Date).AddDays(-30) | Select-Object DeviceFriendlyName,DeviceID,DeviceModel,DeviceOS,ClientType,ClientVersion,LastSuccessSync

        if (-NOT ($NULL -eq $ID.DeviceID)) {
            $Item = [PSCustomObject]@{
                'Name' = $User.DisplayName
                'UPN' = $User.UserPrincipalName
                'Email' = $User.PrimarySMTPAddress
                'Device Friendly Name' = ($ID.DeviceFriendlyName | Out-String).Trim()
                'Device ID' = ($ID.DeviceID | Out-String).Trim()
                'Device Model' = ($ID.DeviceModel  | Out-String).Trim()
                'Device OS' = ($ID.DeviceOS | Out-String).Trim()
                'Client Type' = ($ID.ClientType | Out-String).Trim()
                'Client Version' = ($ID.ClientVersion | Out-String).Trim()
                'Last Success Sync' = ($ID.LastSuccessSync | Out-String).Trim()
            }
            $Rollup.Add($Item) > $Null
        }
        $i++
        Write-Progress -activity "Researching Devices" -status "Group: $b of $Number User: $i of $($Users.Count)" -percentComplete (($i / $Users.Count) * 100)
    }
    $b++
    $Rollup | Export-CSV "C:\Users\rmartin\Documents\Corp Mobile Devices - 9-15-2020.csv" -Append -NoTypeInformation
    Clear-Variable -Name 'Users'
    Clear-Variable -Name 'Rollup'
    $Inc = $Inc+$groupamount
}

Thank you everyone for your help and time,

Rob M

Not really following what you are looking for. The data is by user, not by device. If you want to see how many devices instances there are, you could use grouping:

$temp = @"
Name, Email,DeviceId,Last Sync Success
Joe Smith,jsmith@company.com,3f187194-9914-452c-9b73-2deb21b781e8,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Tim Johnson,tjohnso@company.com,58e3d6a4-e683-43fe-830e-3295a231caa4,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Sally Franklin,sfranklin@company.com,6bd46b6e-c544-472a-a93b-938d87bb04d7,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Joe Smith,jsmith@company.com,3f187194-9914-452c-9b73-2deb21b781e8,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Tim Johnson,tjohnso@company.com,58e3d6a4-e683-43fe-830e-3295a231caa4,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Tim Johnson,tjohnso@company.com,58e3d6a4-e683-43fe-830e-3295a231caa4,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Joe Smith,jsmith@company.com,3f187194-9914-452c-9b73-2deb21b781e8,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Joe Smith,jsmith@company.com,3f187194-9914-452c-9b73-2deb21b781e8,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Sally Franklin,sfranklin@company.com,6bd46b6e-c544-472a-a93b-938d87bb04d7,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Joe Smith,jsmith@company.com,3f187194-9914-452c-9b73-2deb21b781e8,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
Sally Franklin,sfranklin@company.com,6bd46b6e-c544-472a-a93b-938d87bb04d7,(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))
"@ |ConvertFrom-CSV

$grp = $temp | Group-Object -Property DeviceId
Count Name                      Group
----- ----                      -----
    5 3f187194-9914-452c-9b7... {@{Name=Joe Smith; Email=jsmith@company.com; DeviceId=3f187194-9914-452c-9b73-2deb21b781e8; Last Sync Success=(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))}, @{Name=Joe Smith; Email=jsmith...
    3 58e3d6a4-e683-43fe-830... {@{Name=Tim Johnson; Email=tjohnso@company.com; DeviceId=58e3d6a4-e683-43fe-830e-3295a231caa4; Last Sync Success=(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))}, @{Name=Tim Johnson; Email=t...
    3 6bd46b6e-c544-472a-a93... {@{Name=Sally Franklin; Email=sfranklin@company.com; DeviceId=6bd46b6e-c544-472a-a93b-938d87bb04d7; Last Sync Success=(Get-Date).AddDays(-(Get-Random -Minimum 1 -Maximum 5))}, @{Name=Sally Franklin;...

That’s not quite what I’m hoping to do, but instead what I need is if there is an instance of a device, I need the Name, UPN, and Email Field fully filled out.

Each device in it’s own row I guess would be the simplest way to explain it.

Thanks,

Rob

As far as PowerShell is concerned, when you import a CSV everything between two commas is a single value. Having multiple pieces of data inside of one cell is not expected behavior and PowerShell doesn’t have an automatic way to handle it. You could set up some logic to break up those values as discussed in this TechNet post. It’s hard to tell without the actual CSV file, but it looks like the data in those cells might be separated by newlines. If that’s true, you can do -split “`n” to break those cells into separate values, and then associate the separated values with the correct username &etc. That should give you a complete line for each device.

A better solution would be to fix this information at the source, so that each device is listed with complete information by itself. Why is the CSV like this in the first place?

Thanks for that information Grokkit. I can explain the need for the CSV file, as well as the other method I have commented out at the top of the script currently.

We have a user base of about 50K users, so generally if I’m only trying to look at one location of our company, I’ll pull the username data for that particular location rather than trying to use get-exomailbox -filter … it’s just quicker that way. But there’s no reason I can’t pull the data on the fly another way if it allows me to have each device on it’s own row.

The CSV file I use to populate the user data for the rest of the script only has 3 columns, UserPrincipalName, PrimarySMTPAddress, and DisplayName.

Ok, so you’re more or less stuck with ingesting this file as your input as it is. You can help yourself by looking at the CSV in its raw format in Notepad or another text editor. You’ll probably see data that looks like this (based on your example):

"Lastname2,FirstName",2345678@company.com,flastname2@company.com,"iPad (6th generation)
iPhone 8","OKINRCLNPP6DV096JOQNLDAO10
TSPBLIVTBL4D1DQMS9ANCR8RJK
D8338BC0699F41029D70C80D76CE7312","iPad7C5
iPhone10C4
Outlook for iOS and Android","iOS 13.7 17H35
iOS 13.7 17H35
iOS 13.7","EAS
EAS
Outlook","16.1
16.1
1.0","Tuesday, September 15, 2020 2:30:45 PM
Tuesday, September 15, 2020 2:30:35 PM
Tuesday, September 15, 2020 2:23:40 PM"

Even though it’s spread across multiple lines, this is only one row from your CSV. The newline characters that separate the multiple pieces of data inside cells are being preserved inside the double quotes and are reproduced literally when looking at the CSV as unformatted text.

You won’t be able to process this directly because newline characters are also used to separate rows in the CSV, so if you treat newline as a delimiter you will get bad results when you import the file. However, once these values are stored in a PowerShell object you should be able to process each one and split it on the newline character to get the individual pieces.

So, if you process each CSV row of data using a foreach loop, you could build new lines as you go (for each individual device discovered when you split the cells containing multiple values) and associate the username and other data with the device data, probably at the same time inside that foreach loop. With that much data it’ll probably take awhile, but it would definitely be faster than editing by hand.

A simple version of the script would attempt to split every cell to find multiple values, but you could probably make it more efficient if you can be sure that some cells (such as “Name”) will not contain multiple values and exclude them from the split operation.

Based on the current data screenshot, you can read in that file as a csv and parse the rows by splitting on newline characters:

import-csv input.csv | Foreach-Object {
    $deviceFNs = $_.'Device Friendly Name' -split '\r?\n'
    $deviceIDs = $_.'Device ID' -split '\r?\n'
    $deviceModels = $_.'Device Model' -split '\r?\n'
    $deviceOSes = $_.'Device OS' -split '\r?\n'
    $clientTypes = $_.'Client Type' -split '\r?\n'
    $clientVers = $_.'Client Version' -split '\r?\n'
    $Syncs = $_.'Last Success Sync' -split '\r?\n'
    for ($i = 0; $i -lt $deviceIDs.Count; $i++) {
        [pscustomobject]@{
            'Lastname,Firstname'=$_.'Lastname,Firstname'
            UPN = $_.UPN
            Email = $_.Email
            'Device ID' = $deviceIDs[$i]
            'Device Model' = $deviceModels[$i]
            'Device Friendly Name' = $deviceFNs[$i]
            'Device OS' = $devicesOSes[$i]
            'Client Type' = $clientTypes[$i]
            'Client Version' = $clientVers[$i]
            'Last Success Sync' = $Syncs[$i]
        }
    }
}