1

I found a similar post regarding the problem in the link below.

How to fetch first column from given powershell array?

I am not able to directly convert it to a table as some fields are missing and do operations.

Customer ID        Client Name        Computer Name        Computer Brand        Duration        Connection Time        Lang
123                first last         127.0.0.1            lenovo                10:00           8/18/2019 6:00 PM      Eng
1                  lastname           127.0.0.2            apple                 2:30:00         8/18/2019 1:00 AM      Chn  
86                 user3              127.0.0.1            dell                                  8/18/2019 2:00 PM 
21                 user4              127.0.0.4            apple                 30:00           8/17/2019 1:00 PM      Eng

I want to first filter with a specific user who is connected for more than 30 minutes and then list its id.

Update

The result should be

1
21

because they are connected for 30min and over.

19
  • 1
    Is that you input or your required output? The link you gave also answers your questions in regards of filtering. What's your problem there? Commented Aug 25, 2019 at 6:12
  • Please show us what your array really looks like. You are showing us some output, but from that we cannot say if the original array is an array of objects, or just lines of text with spaces and/or tabs in between the fields. Commented Aug 25, 2019 at 10:47
  • @vrdse I have edited and shown my desired output. Commented Aug 25, 2019 at 11:33
  • @Theo I have edited to have a better view. My question is the same as the link I shared, the only difference is that I can't create a table out of it. There are many other fileds which I have not shown. Commented Aug 25, 2019 at 11:34
  • 1
    Looks a lot nicer indeed, but.. this does not seem to be an array, just some textual output of an array or table. We need to know what separates the fields, especially since some fields are empty. In your example the whitespaces are all space characters, so to me this looks like a Fixed-Width text file. Is that the case here? Commented Aug 25, 2019 at 11:42

2 Answers 2

1

If the data you show is indeed the output of a Fixed-Width file, you need to try and get the widths for each field in order to parse it. A handicap here is that the original header names contain a space character and we need to replace that by an underscore.

For that, you can use the below function:

function ConvertFrom-FixedWith {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string[]]$Content
    )

    $splitter   = '§¤¶'             # some unlikely string: Alt-21, [char]164, Alt-20  
    $needQuotes = '^\s+|[",]|\s+$'  # quote the fields if needed

    function _FWClean ([string]$field) {
        # internal helper function to clean a field value with regards to quoted fields
        $field = $_.Trim() -replace '(?<!\\)\\"|""', '§DQUOTE¶'
        if ($field -match '^"(.*)"$')  { $field = $matches[1] }
        if ($field -match $needQuotes) { $field = '"{0}"' -f $field }
        return $field -replace '§DQUOTE¶', '""'
    }

    # try and calculate the field widths using the first header line
    # this only works if none of the header names have spaces in them
    # and where the headers are separated by at least one space character.

    Write-Verbose "Calculating column widths using first row"
    $row = ($Content[0] -replace '\s+', ' ').Trim()
    $fields = @($row -split ' ' ) # | ForEach-Object { _FWClean $_ })
    $ColumnBreaks = for ($i = 1; $i -lt $fields.Length; $i++) {
        $Content[0].IndexOf($fields[$i]) 
    }
    $ColumnBreaks = $ColumnBreaks | Sort-Object -Descending

    Write-Verbose "Splitting fields and generating output"
    $Content | ForEach-Object {
        if ($null -ne $_ -and $_ -match '\S') {
            $line = $_
            # make sure lines that are too short get padded on the right
            if ($line.Length -le $ColumnBreaks[0]) { $line = $line.PadRight(($ColumnBreaks[0] + 1), ' ') }
            # add the splitter string on every column break point
            $ColumnBreaks | ForEach-Object { 
                $line = $line.Insert($_, $splitter)
            }
            # split on the splitter string, trim, and dedupe possible quotes
            # then join using the delimiter character
            @($line -split $splitter | ForEach-Object { _FWClean $_ }) -join ','
        }
    } | ConvertFrom-Csv    # the result is an array of PSCustomObjects
}

With that function in place, parsing the text can be done like so:

$text = @"
Customer_ID        Client_Name        Computer_Name        Computer_Brand        Duration        Connection_Time        Lang
123                first last         127.0.0.1            lenovo                10:00           8/18/2019 6:00 PM      Eng
1                  lastname           127.0.0.2            apple                 2:30:00         8/18/2019 1:00 AM      Chn  
86                 user3              127.0.0.1            dell                                  8/18/2019 2:00 PM 
21                 user4              127.0.0.4            apple                 30:00           8/17/2019 1:00 PM      Eng
"@ -split '\r?\n'

# replace the single space characters in the header names by underscore
$text[0] = $text[0] -replace '(\w+) (\w+)', '$1_$2'

# the 'ConvertFrom-FixedWith' function takes a string array as input
$table = ConvertFrom-FixedWith -Content $text

#output on screen
$table | Format-Table -AutoSize

# export to CSV file
$table | Export-Csv -Path 'D:\test.csv' -NoTypeInformation

Output (on screen)

Customer ID Client Name Computer Name Computer Brand Duration Connection Time   Lang
----------- ----------- ------------- -------------- -------- ---------------   ----
123         first last  127.0.0.1     lenovo         10:00    8/18/2019 6:00 PM Eng 
1           lastname    127.0.0.2     apple          2:30:00  8/18/2019 1:00 AM Chn 
86          user3       127.0.0.1     dell                    8/18/2019 2:00 PM     
21          user4       127.0.0.4     apple          30:00    8/17/2019 1:00 PM Eng 

If your input $text is already a string array storing all the ines as we see them in your question, then leave out the -split '\r?\n'


Having parsed the input to a table of PsCustomObjects, you can get the customers that are connected for 30 minutes or more with the help of another small helper function:

function Get-DurationInMinutes ([string]$Duration) {
    $h, $m, $s = (('0:{0}' -f $Duration) -split ':' | Select-Object -Last 3)
    return [int]$h * 60 + [int]$m
}

($table | Where-Object { (Get-DurationInMinutes $_.Duration) -ge 30 }).Customer_ID

This will output

1
21


Update


Now that we finally know the data is from a TAB delimited CSV file, you don't need the ConvertFrom-FixedWith function.

Simply import the data using if it comes from a file

$table = Import-Csv -Path 'D:\customers.csv' -Delimiter "`t"

Or, if it comes from the output of another command as string or string array:

$table = $original_output | ConvertFrom-Csv -Delimiter "`t"

Then, use the Get-DurationInMinutes helper function just like above to get the Customer ID's that are connected for more than 30 minutes:

function Get-DurationInMinutes ([string]$Duration) {
    $h, $m, $s = (('0:{0}' -f $Duration) -split ':' | Select-Object -Last 3)
    return [int]$h * 60 + [int]$m
}

($table | Where-Object { (Get-DurationInMinutes $_.Duration) -ge 30 }).'Customer ID'
Sign up to request clarification or add additional context in comments.

1 Comment

although my output is of different width I will try this out. Maybe it will solve the issue I am facing. I'll update you the output once I try this out.
0

Uhh. I'm surprised there's not a canonical way to do this. Based on https://www.reddit.com/r/PowerShell/comments/211ewa/how_to_convert_fixedwidth_to_pipedelimited_or/.

# 0                  19                 38                   59                    81              97                     120 123
# Customer ID        Client Name        Computer Name        Computer Brand        Duration        Connection Time        Lang
# 123                first last         127.0.0.1            lenovo                10:00           8/18/2019 6:00 PM      Eng
# 1                  lastname           127.0.0.2            apple                 2:30:00         8/18/2019 1:00 AM      Chn
# 86                 user3              127.0.0.1            dell                                  8/18/2019 2:00 PM
# 21                 user4              127.0.0.4            apple                 30:00           8/17/2019 1:00 PM      Eng


$cols = 0,19,38,59,81,97,120,123 # fake extra column at the end, assumes all rows are that wide

$firstline = get-content columns.txt | select -first 1
$headers = for ($i = 0; $i -lt $cols.count - 1; $i++) {
  $firstline.substring($cols[$i], $cols[$i+1]-$cols[$i]).trim()
}

# string Substring(int startIndex, int length)

$lines = Get-Content columns.txt | select -skip 1 
$lines | ForEach {
  $hash = [ordered]@{}
  for ($i = 0; $i -lt $headers.length; $i++) {
    $hash += @{$headers[$i] = $_.substring($cols[$i], $cols[$i+1]-$cols[$i]).trim()}
  }
  [pscustomobject]$hash
} 

Output:

PS /Users/js/foo> ./columns | ft

Customer ID Client Name Computer Name Computer Brand Duration Connection Time   Lan
----------- ----------- ------------- -------------- -------- ---------------   ---
123         first last  127.0.0.1     lenovo         10:00    8/18/2019 6:00 PM Eng
1           lastname    127.0.0.2     apple          2:30:00  8/18/2019 1:00 AM Chn
86          user3       127.0.0.1     dell                    8/18/2019 2:00 PM 
21          user4       127.0.0.4     apple          30:00    8/17/2019 1:00 PM Eng

1 Comment

I will try it out and update you the with the output.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.