none
Convert tabular string output to an array with PowerShell 2.0 RRS feed

  • Question

  • TL;DR: I am trying to convert tabular string output to an array in PowerShell 2.0.

    I'm trying to automate a bunch of workstation-level stuff related to deploying MBAM/BitLocker on a large fleet of existing Dell workstations. Mainly BIOS/UEFI and TPM stuff. The current task I'm working on is forcing the first hard disk in the boot order to be the first and only boot device.

    To do this, you use the Dell Command | Configure tool cctk.exe with the BootOrder parameter. Here is example of running that command and its output just to display the current boot device order:



    PS C:\Users\redacted\Desktop\> .\cctk.exe BootOrder
    
     DeviceStatus DeviceNumber     DeviceType    Shortform  DeviceDescription
     -------------------------------------------------------------------------
      Enabled         2             Hard Disk          hdd  Internal HDD
      Enabled         1           Floppy Disk       floppy  Diskette Drive
      Enabled         3            USB device       usbdev  USB Storage Device
      Enabled         4                 CDRom        cdrom  CD/DVD/CD-RW Drive
      Enabled         5          Embedded NIC       embnic  Onboard NIC
     -------------------------------------------------------------------------
    You can use DeviceNumber or shortform to set the boot order.
    Example1: cctk BootOrder --Sequence=2,1,3 --DisableDevice=1 --EnableDevice=2,3
    Example2: cctk BootOrder --Sequence=cdrom,hdd.2,hdd.1 --DisableDevice=hdd.2 --En
    ableDevice=cdrom,hdd.1
    In file operation, same command can write like BootOrder=+cdrom,-hdd.2,+hdd.1
    
    
    PS C:\Users\redacted\Desktop\>


    Any given computer might have different devices or a different boot order, so that table may look different machine to machine. What I'd like to do is grab that table of boot devices and convert each row into an object in an array using the table header as the name of the attributes in the array.

    Unfortunately, these are Windows 7 devices, so I'm looking at a PowerShell 2.0 way to do this. No ConvertFrom-String. Any tips on how I might accomplish this or at least point me in the best direction would be very useful.


    • Edited by Scott W. Sander Tuesday, June 26, 2018 1:57 PM Sorry, I can't get the fixed width text to display correctly. If you copy it out to a text editor, the fixed width will show the correct output.
    Tuesday, June 26, 2018 1:48 PM

Answers

  • Ah, I see that object creation method results in an array the way I like it.

    Note: your code still pulls in all the help text output when you run "cctk.exe BootOrder". Your example sets a variable that already excludes that stuff. But, like I said already, the code I came up with solves that problem. Using the horizontal line counter ($HyphenLineCounter), it basically stops looping after it gets to the 2nd horizontal line and leaves all the undesired help information behind.

    Here's the updated code:

    [cmdletbinding()]
    Param(
        [string]$ToolLocation = ".\Utilities",
        [string]$ToolFileName = "cctk.exe"
    )
    
    Function Custom-Get-OSArchitecture
    {
        switch ((Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture)
        {
            '32-bit' { 'x86' }
            '64-bit' { 'x64' }
        }
    }
    
    Function Custom-Convert-DeviceRowToObject($Device)
    {
        New-Object System.Management.Automation.PSObject -property @{
    
            $HeaderRow.Substring(0,13).Trim() = $Device.Substring(0,13).Trim()
            $HeaderRow.Substring(14,13).Trim() = $Device.Substring(14,13).Trim()
            $HeaderRow.Substring(27,15).Trim() = $Device.Substring(27,15).Trim()
            $HeaderRow.Substring(42,14).Trim() = $Device.Substring(42,14).Trim()
            $HeaderRow.Substring(55,$HeaderRow.Length - 55).Trim() = $Device.Substring(55,$Device.Length - 55).Trim()
        }
    }
    
    [int]$HyphenLineCounter = 0
    [int]$LineCounter = 0
    [string]$OsArchitecture = Custom-Get-OSArchitecture
        
    [array]$BootDevices = Invoke-Expression "$ToolLocation\$OsArchitecture\$ToolFileName BootOrder" | ForEach-Object {
    
        If ($_ -match "-{3,}")
        {
            $HyphenLineCounter++
        }
        ElseIf (($HyphenLineCounter -lt 2) -and ($_.Trim()))
        {
            If ($LineCounter -eq 0)
            {
                [string]$HeaderRow = $_
            }
            Else
            {
                Custom-Convert-DeviceRowToObject $_
            }
    
            $LineCounter++
        }
    }
    
    If ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent)
    {
        $BootDevices | Format-Table -AutoSize
    }

    Yes, I'm still starting my function names with "Custom-" and I know it doesn't follow the PS convention. I do it so I don't accidentally step on a built-in PS cmdlet or function, though I realize in the specific cases in my code, that isn't likely.

    Input string (also includes a first line that is blank that I can't get to display on the forum):

     DeviceStatus DeviceNumber     DeviceType    Shortform  DeviceDescription
     -------------------------------------------------------------------------
      Enabled         2             Hard Disk          hdd  Internal HDD
      Enabled         1           Floppy Disk       floppy  Diskette Drive
      Enabled         3            USB device       usbdev  USB Storage Device
      Enabled         4                 CDRom        cdrom  CD/DVD/CD-RW Drive
      Enabled         5          Embedded NIC       embnic  Onboard NIC
     -------------------------------------------------------------------------
    You can use DeviceNumber or shortform to set the boot order.
    Example1: cctk BootOrder --Sequence=2,1,3 --DisableDevice=1 --EnableDevice=2,3
    Example2: cctk BootOrder --Sequence=cdrom,hdd.2,hdd.1 --DisableDevice=hdd.2 --En
    ableDevice=cdrom,hdd.1
    In file operation, same command can write like BootOrder=+cdrom,-hdd.2,+hdd.1
    
    
    

    Output array ($BootDevices | Format-Table -Autosize):

    DeviceNumber DeviceDescription  Shortform DeviceType   DeviceStatus
    ------------ -----------------  --------- ----------   ------------
    2            Internal HDD       hdd       Hard Disk    Enabled
    1            Diskette Drive     floppy    Floppy Disk  Enabled
    3            USB Storage Device usbdev    USB device   Enabled
    4            CD/DVD/CD-RW Drive cdrom     CDRom        Enabled
    5            Onboard NIC        embnic    Embedded NIC Enabled

    Thanks again!







    Wednesday, June 27, 2018 5:41 PM

All replies

  • I cannot test or reproduce because I don't have Windows 7 anymore but did the .Net substring method exist already on Powershell v2? If you have always a fixed width of your columns it would be a kind of easy to cut them into proper pieces.

    Best regards,

    (79,108,97,102|%{[char]$_})-join''

    Tuesday, June 26, 2018 2:11 PM
  • Yeah, the Substring() method exists for strings in PowerShell 2.0.

    I've managed to cut out some whitespace. ($CctkOutput is a variable containing the string output from my OP):


    PS C:\Users\redacted\Desktop> ($CctkOutput[1..$($CctkOutput.Count - 2)]).Trim()
    DeviceStatus DeviceNumber     DeviceType    Shortform  DeviceDescription
    -------------------------------------------------------------------------
    Enabled         2             Hard Disk          hdd  Internal HDD
    Enabled         1           Floppy Disk       floppy  Diskette Drive
    Enabled         3            USB device       usbdev  USB Storage Device
    Enabled         4                 CDRom        cdrom  CD/DVD/CD-RW Drive
    Enabled         5          Embedded NIC       embnic  Onboard NIC
    -------------------------------------------------------------------------
    You can use DeviceNumber or shortform to set the boot order.
    Example1: cctk BootOrder --Sequence=2,1,3 --DisableDevice=1 --EnableDevice=2,3
    Example2: cctk BootOrder --Sequence=cdrom,hdd.2,hdd.1 --DisableDevice=hdd.2 --En
    ableDevice=cdrom,hdd.1
    In file operation, same command can write like BootOrder=+cdrom,-hdd.2,+hdd.1
    PS C:\Users\redacted\Desktop>


    What I'm trying to do now is to cut out everything after the second line with the hyphens (without hardcoding the number of lines because it could vary) and then remove the hyphen lines themselves. At that point I'll be left with the table where the first line is the header. Then I can mess with Substring() to get it into an array. But what's the best way to detect that 2nd line of hyphens and cut everything out after.


    Tuesday, June 26, 2018 2:24 PM
  • Getting there....

    $CctkCondensedOutput = ($CctkOutput[1..$($CctkOutput.Count - 2)]).Trim()
    
    [int]$HyphenLineCounter = 0
    
    $CctkCondensedOutput | ForEach-Object {
    
        If ($_ -match "^-{2,}")
        {
            $HyphenLineCounter++
        }
        ElseIf ($HyphenLineCounter -lt 2)
        {
            Write-Host "$_"
        }
    }
    DeviceStatus DeviceNumber     DeviceType    Shortform  DeviceDescription
    Enabled         2             Hard Disk          hdd  Internal HDD
    Enabled         1           Floppy Disk       floppy  Diskette Drive
    Enabled         3            USB device       usbdev  USB Storage Device
    Enabled         4                 CDRom        cdrom  CD/DVD/CD-RW Drive
    Enabled         5          Embedded NIC       embnic  Onboard NIC

    Tuesday, June 26, 2018 2:44 PM
  • Looks like I got it.

    Code:

    # ==============================================================================
    #  SET COMMANDLINE PARAMETERS
    # ==============================================================================
    
    [cmdletbinding()]
    Param(
        [string]$CctkLocation = ".\Utilities"
    )
    
    
    # ==============================================================================
    #  SET CUSTOM FUNCTIONS
    # ==============================================================================
    
    Function Custom-Convert-DeviceRowToObject($Device)
    {
        [hashtable]$ObjectProperty = @{}
    
        $ObjectProperty.Add($HeaderRow.Substring(0,13).Trim(),$Device.Substring(0,13).Trim())
        $ObjectProperty.Add($HeaderRow.Substring(14,13).Trim(),$Device.Substring(14,13).Trim())
        $ObjectProperty.Add($HeaderRow.Substring(27,15).Trim(),$Device.Substring(27,15).Trim())
        $ObjectProperty.Add($HeaderRow.Substring(42,14).Trim(),$Device.Substring(42,14).Trim())
        $ObjectProperty.Add($HeaderRow.Substring(55,$HeaderRow.Length - 55).Trim(),$Device.Substring(55,$Device.Length - 55).Trim())
            
        $DeviceObject = New-Object -TypeName psobject -Property $ObjectProperty 
    
        Return $DeviceObject
    }
    
    
    # ==============================================================================
    #  RUN
    # ==============================================================================
    
    # Initialize variables.
    [int]$HyphenLineCounter = 0
    [int]$LineCounter = 0
    $HeaderRow = $null
    $BootDevices = $null
    $ObjectProperty = $null
    $DeviceObject = $null
    
    # Get the boot order.
    $CctkOutput = Invoke-Expression "$CctkLocation\cctk.exe BootOrder"
    
    # Loop through each row in the output.
    ForEach ($OutputRow in $CctkOutput)
    {
        # If a line containing two or more hyphens is encountered, increment a
        # counter. The evaluated output should contain two (and only two) horizontal
        # lines made up of hyphens, which demarcate the data.
        If ($OutputRow -match "-{2,}")
        {
            $HyphenLineCounter++
        }
    
        # Do nothing if a blank line is encountered.
        ElseIf (!($OutputRow -match ".+"))
        {
    
        }
        
        # Grab all reamining lines before the second horizontal line, which should
        # only include non-blank lines and non-horizontal lines. It is expected to
        # include both the table header and the table data.
        ElseIf ($HyphenLineCounter -lt 2)
        {
            # If this is the first evaluated row of content, consider it the table
            # header and store it.
            If ($LineCounter -eq 0)
            {
                [string]$HeaderRow = $OutputRow
            }
    
            # Convert each table data row from a string to an object.
            Else
            {
                [array]$BootDevices += @(Custom-Convert-DeviceRowToObject $OutputRow)
            }
            
            # Increment a counter representing the number of important table lines.
            $LineCounter++
        }
        
    }

    Output of $BootDevices | Format-Table:


    Shortform DeviceType   DeviceStatus DeviceDescription  DeviceNumber
    --------- ----------   ------------ -----------------  ------------
    hdd       Hard Disk    Enabled      Internal HDD       2
    floppy    Floppy Disk  Enabled      Diskette Drive     1
    usbdev    USB device   Enabled      USB Storage Device 3
    cdrom     CDRom        Enabled      CD/DVD/CD-RW Drive 4
    embnic    Embedded NIC Enabled      Onboard NIC        5

    Due to PowerShell's way of organizing hashtables, the $BootDevices array columns are not in the same order as the table, but the order doesn't matter for my use case.


    • Edited by Scott W. Sander Tuesday, June 26, 2018 10:00 PM Previously posted code had a small error I needed to fix.
    Tuesday, June 26, 2018 9:36 PM
  • Hashtables are not ordered, by definition.


    -- Bill Stewart [Bill_Stewart]

    Tuesday, June 26, 2018 9:50 PM
    Moderator
  • Replace this:

        [hashtable]$ObjectProperty = @{}
    
        $ObjectProperty  = New-Object -TypeName psobject -Property $objectProperty
        $ObjectProperty.Add($HeaderRow.Substring(0,13).Trim(),$Device.Substring(0,13).Trim())
        $ObjectProperty.Add($HeaderRow.Substring(14,13).Trim(),$Device.Substring(14,13).Trim())
        $ObjectProperty.Add($HeaderRow.Substring(27,15).Trim(),$Device.Substring(27,15).Trim())
        $ObjectProperty.Add($HeaderRow.Substring(42,14).Trim(),$Device.Substring(42,14).Trim())
        $ObjectProperty.Add($HeaderRow.Substring(55,$HeaderRow.Length - 55).Trim(),$Device.Substring(55,$Device.Length - 55).Trim())
            
        $DeviceObject = New-Object -TypeName psobject -Property $ObjectProperty 
    


    With this:

        [pscustomobject][ordered]@{
            $HeaderRow.Substring(0,13).Trim() = $Device.Substring(0,13).Trim()
            $HeaderRow.Substring(14,13).Trim() = $Device.Substring(14,13).Trim()
            $HeaderRow.Substring(27,15).Trim() = $Device.Substring(27,15).Trim()
            $HeaderRow.Substring(42,14).Trim() = $Device.Substring(42,14).Trim()
            $HeaderRow.Substring(55,$HeaderRow.Length - 55).Trim() = $Device.Substring(55,$Device.Length - 55).Trim()
        }
    

    You method of object creation is pretty much obsolete. This method creates a custom object and retains the declaration order of the properties.


    \_(ツ)_/

    Tuesday, June 26, 2018 10:00 PM
  • It would look like this;

    function Custom-Convert-DeviceRowToObject($Device){
        [pscustomobject][ordered]@{
            $HeaderRow.Substring(0,13).Trim() = $Device.Substring(0,13).Trim()
            $HeaderRow.Substring(14,13).Trim() = $Device.Substring(14,13).Trim()
            $HeaderRow.Substring(27,15).Trim() = $Device.Substring(27,15).Trim()
            $HeaderRow.Substring(42,14).Trim() = $Device.Substring(42,14).Trim()
            $HeaderRow.Substring(55,$HeaderRow.Length - 55).Trim() = $Device.Substring(55,$Device.Length - 55).Trim()
        }
    }
    P.S. - "Custom" is not a verb.  PS nomenclature is "Verb-Noun"  so it should be: "Convert-DeviceRowToObject"


    \_(ツ)_/


    • Edited by jrv Tuesday, June 26, 2018 10:05 PM
    Tuesday, June 26, 2018 10:03 PM
  • The following would be a better approach:

    [cmdletbinding()]
    Param(
        [string]$CctkLocation = '.\Utilities'
    )
    
    [int]$HyphenLineCounter = 0
    [int]$LineCounter = 0
    Invoke-Expression "$CctkLocation\cctk.exe BootOrder" |
        ForEach-Object{
            If ($_ -match "-{2,}"){
                # If a line containing two or more hyphens is encountered, increment a
                # counter. The evaluated output should contain two (and only two) horizontal
                # lines made up of hyphens, which demarcate the data.
                $HyphenLineCounter++
            }elseif(!($_ -match ".+")){ 
                # Do nothing if a blank line is encountered.
                # Grab all reamining lines before the second horizontal line, which should
                # only include non-blank lines and non-horizontal lines. It is expected to
                # include both the table header and the table data.
            }elseif ($HyphenLineCounter -lt 2){
                # If this is the first evaluated row of content, consider it the table
                # header and store it.
                If ($LineCounter -eq 0){
                    [string]$HeaderRow = $_
                }else{ 
                    # Convert each table data row from a string to an object.
                    [pscustomobject][ordered]@{
                        DeviceStatus = $Device.Substring(0,13).Trim()
                        DeviceNumber = $Device.Substring(14,13).Trim()
                        DeviceType = $Device.Substring(27,15).Trim()
                        Shortform = $Device.Substring(42,14).Trim()
                        DeviceDescription = $Device.Substring(55,$Device.Length - 55).Trim()
                    }
                }
                $LineCounter++
            }
        }
    }
    


    \_(ツ)_/


    • Edited by jrv Tuesday, June 26, 2018 10:21 PM
    Tuesday, June 26, 2018 10:17 PM
  • Microsoft no longer supports PowerShell WMF V2 and has noted that it should be removed from or blocked on all systems.  It is considered a security risk.

    MS does not support any older software beyond two versions.  Some exceptions do exist for large subsystems.


    \_(ツ)_/

    Tuesday, June 26, 2018 10:19 PM
  • The following is really all you need to do:

    Param( [string]$CctkLocation = '.\Utilities' ) $lines = Invoke-Expression "$CctkLocation\cctk.exe BootOrder"

    # remove blank lines, hyphens and header row. $rows = $lines | where{$_.Trim() -and $_ -notmatch '^-'} | select -skip 1 $rows | ForEach-Object{ [pscustomobject][ordered]@{ DeviceStatus = $_.Substring(0,13).Trim() DeviceNumber = $_.Substring(14,13).Trim() DeviceType = $_.Substring(27,15).Trim() Shortform = $_.Substring(42,14).Trim() DeviceDescription = $_.Substring(55,$_.Length - 55).Trim() } }



    \_(ツ)_/







    • Edited by jrv Tuesday, June 26, 2018 10:31 PM
    • Proposed as answer by BOfH-666 Tuesday, June 26, 2018 11:13 PM
    • Unproposed as answer by Scott W. Sander Wednesday, June 27, 2018 4:19 PM
    Tuesday, June 26, 2018 10:26 PM
  • Thanks for the tips! Unfortunately, all of your suggested code only works on PowerShell 3.0 and up and I need it to work on PowerShell 2.0. Believe me, I prefer the PowerShell 3.0+ way of custom object creation. That said, you did find some useful ways to reduce the amount of code.

    I recognize that PS 2.0 is deprecated, but I can only work with what is available on the Windows 7 machines deployed at my employer and I have next to zero control over what version is currently installed our systems. I'll take the opportunity to remind the responsible party.

    As far as ForEach vs. ForEach-Object, I recognize that the latter results in more concise code but I (and my colleagues) generally prefer the readability of the former.

    Again, thanks for the additional pointers.

    Wednesday, June 27, 2018 12:30 AM
  • PS 2 is not deprecated.  It should not be used and either disabled or removed on any system that supports removal.

    The same code does work in PS2 just so you know.  Just change the object generator to use the hash.  You lose [ordered] as it does not exist in Net 2.0.

    To use in V2 just remove the "[ordered]" specifier.


    \_(ツ)_/

    Wednesday, June 27, 2018 12:35 AM
  • The following is really all you need to do:

    Param( [string]$CctkLocation = '.\Utilities' ) $lines = Invoke-Expression "$CctkLocation\cctk.exe BootOrder"

    # remove blank lines, hyphens and header row. $rows = $lines | where{$_.Trim() -and $_ -notmatch '^-'} | select -skip 1 $rows | ForEach-Object{ [pscustomobject][ordered]@{ DeviceStatus = $_.Substring(0,13).Trim() DeviceNumber = $_.Substring(14,13).Trim() DeviceType = $_.Substring(27,15).Trim() Shortform = $_.Substring(42,14).Trim() DeviceDescription = $_.Substring(55,$_.Length - 55).Trim() } }



    \_(ツ)_/

    This isn't meant as a critique, but I tried this code (minus "[ordered]") on a Windows 7 SP1 machine with only PowerShell 2.0 and it resulted in objects where the attributes were set to pieces of the horizontal lines and help information rather than being limited to only the boot devices. I'm sure it's something you could solve pretty easily. Just letting you know. I've provided the output I get with your code below. Part of the problem is the regex '^-' because the horizontal lines in the output actually start with a space. '--' seems to work better, but all the help stuff is still in there.

    Name                           Value
    ----                           -----
    DeviceNumber                   -------------
    DeviceDescription              -------------------
    Shortform                      --------------
    DeviceType                     ---------------
    DeviceStatus                   ------------
    DeviceNumber                   2
    DeviceDescription              Internal HDD
    Shortform                      hdd
    DeviceType                     Hard Disk
    DeviceStatus                   Enabled
    DeviceNumber                   1
    DeviceDescription              Diskette Drive
    Shortform                      floppy
    DeviceType                     Floppy Disk
    DeviceStatus                   Enabled
    DeviceNumber                   3
    DeviceDescription              USB Storage Device
    Shortform                      usbdev
    DeviceType                     USB device
    DeviceStatus                   Enabled
    DeviceNumber                   4
    DeviceDescription              CD/DVD/CD-RW Drive
    Shortform                      cdrom
    DeviceType                     CDRom
    DeviceStatus                   Enabled
    DeviceNumber                   5
    DeviceDescription              Onboard NIC
    Shortform                      embnic
    DeviceType                     Embedded NIC
    DeviceStatus                   Enabled
    DeviceNumber                   -------------
    DeviceDescription              -------------------
    Shortform                      --------------
    DeviceType                     ---------------
    DeviceStatus                   ------------
    DeviceNumber                   viceNumber or
    DeviceDescription              rder.
    Shortform                      et the boot or
    DeviceType                     shortform to s
    DeviceStatus                   You can use D
    DeviceNumber                   BootOrder --
    DeviceDescription              ce=1 --EnableDevice=2,3
    Shortform                      --DisableDevic
    DeviceType                     Sequence=2,1,3
    DeviceStatus                   Example1: cct
    DeviceNumber                   BootOrder --
    DeviceDescription              -DisableDevice=hdd.2 --EnableDevice=cdrom,hdd.1
    Shortform                      hdd.2,hdd.1 --
    DeviceType                     Sequence=cdrom,
    DeviceStatus                   Example2: cct
    DeviceNumber                   ion, same com
    DeviceDescription              r=+cdrom,-hdd.2,+hdd.1
    Shortform                      like BootOrder
    DeviceType                     mand can write
    DeviceStatus                   In file opera

    I did have a question about the custom object in general. In the last piece of code I posted, writing out $BootDevices with Format-Table displays each boot device in its own row with each attribute in its own column named after the related table header item. In the custom object created via your more modern method of custom object creation, the output only shows two columns: "Name" and "Value":

    Name                           Value
    ----                           -----
    DeviceNumber                   2
    DeviceDescription              Internal HDD
    Shortform                      hdd
    DeviceType                     Hard Disk
    DeviceStatus                   Enabled
    DeviceNumber                   1
    DeviceDescription              Diskette Drive
    Shortform                      floppy
    DeviceType                     Floppy Disk
    DeviceStatus                   Enabled
    DeviceNumber                   3
    DeviceDescription              USB Storage Device
    Shortform                      usbdev
    DeviceType                     USB device
    DeviceStatus                   Enabled
    DeviceNumber                   4
    DeviceDescription              CD/DVD/CD-RW Drive
    Shortform                      cdrom
    DeviceType                     CDRom
    DeviceStatus                   Enabled
    DeviceNumber                   5
    DeviceDescription              Onboard NIC
    Shortform                      embnic
    DeviceType                     Embedded NIC
    DeviceStatus                   Enabled

    Compare that to the five-column output from my previous post. I know the data can be worked with either way, but I prefer the display method where the columns are titled by the parameter instead of just "Name" and "Value". Is there an easy way to make it how I like with the modern custom object method?

    By the way, I did end up switching to using ForEach-Object after all. It did not occur to me that adding an object to an array with += in a loop is inefficient until I read this. Makes perfect sense after being confronted with it.

    Wednesday, June 27, 2018 3:41 PM
  • Remove the "^" character from the match.


    \_(ツ)_/

    Wednesday, June 27, 2018 3:58 PM
  • That regex filters out the line containing "CD/DVD/CD-RW Drive". '--' works but still includes all the help crap. It doesn't matter, I've already got working code I'm happy with.

    Did you see my question about the object columns near the end of my last post? I'll repeat it here in case you missed it:

    "Compare that to the five-column output from my previous post*. I know the data can be worked with either way, but I prefer the display method where the columns are titled by the parameter instead of just 'Name' and 'Value'. Is there an easy way to make it how I like with the modern custom object method?"

    * "previous post" in this case refers to my post yesterday starting with "Looks like I got it."

    Wednesday, June 27, 2018 4:04 PM
  • You have to think. Try to understand what is happening.  I do not have your system so I cannot test everything.

    $rows =$lines | where{$_.Trim() -and$_ -notmatch '\s+--'} | select -skip 1


    \_(ツ)_/

    Wednesday, June 27, 2018 4:33 PM
  • You have the exact string output I'm working with in the original post. I already have code that grabs exactly the data I want and converts it to an array, so I don't need to do any more thinking about that. I'm was just telling you for your own benefit that your code doesn't result in the desired output given the input. If you want to play around with solving it, you can. I don't need you to. As far as I'm concerned, it's solved.

    I did change my script to incorporate some of your ideas, so thanks for the pointers along the way.

    I had another question, but I asked twice without getting answered and it's unrelated to the original purpose of this post so I'll make a new thread about the new question.




    Wednesday, June 27, 2018 4:46 PM
  • This works and is tested on PS2 on Win 7 and other PS2 systems:

    PS D:\scripts> $lines = @'
    >> DeviceStatus DeviceNumber     DeviceType    Shortform  DeviceDescription
    >>  -------------------------------------------------------------------------
    >>   Enabled         2             Hard Disk          hdd  Internal HDD
    >>   Enabled         1           Floppy Disk       floppy  Diskette Drive
    >>   Enabled         3            USB device       usbdev  USB Storage Device
    >>   Enabled         4                 CDRom        cdrom  CD/DVD/CD-RW Drive
    >>   Enabled         5          Embedded NIC       embnic  Onboard NIC
    >>  -------------------------------------------------------------------------
    >> '@ -split "`n"
    >> $rows = $lines | where{$_.Trim() -and $_ -notmatch '^\s+-'} | select -skip 1
    >> $rows |
    >>     ForEach-Object{
    >>         New-Object System.Management.Automation.PSObject -property @{
    >>             DeviceStatus = $_.Substring(0,13).Trim()
    >>             DeviceNumber = $_.Substring(14,13).Trim()
    >>             DeviceType = $_.Substring(27,15).Trim()
    >>             Shortform = $_.Substring(42,14).Trim()
    >>             DeviceDescription = $_.Substring(55,$_.Length - 55).Trim()
    >>         }
    >>     }
    >>
    
    
    DeviceNumber      : 2
    DeviceDescription : Internal HDD
    Shortform         : hdd
    DeviceType        : Hard Disk
    DeviceStatus      : Enabled
    
    DeviceNumber      : 1
    DeviceDescription : Diskette Drive
    Shortform         : floppy
    DeviceType        : Floppy Disk
    DeviceStatus      : Enabled
    
    DeviceNumber      : 3
    DeviceDescription : USB Storage Device
    Shortform         : usbdev
    DeviceType        : USB device
    DeviceStatus      : Enabled
    
    DeviceNumber      : 4
    DeviceDescription : CD/DVD/CD-RW Drive
    Shortform         : cdrom
    DeviceType        : CDRom

    Using Format-Table:

    PS D:\scripts> $rows = $lines | where{$_.Trim() -and $_ -notmatch '^\s+-'} | select -skip 1
    PS D:\scripts> $rows |
    >>     ForEach-Object{
    >>         New-Object System.Management.Automation.PSObject -property @{
    >>             DeviceStatus = $_.Substring(0,13).Trim()
    >>             DeviceNumber = $_.Substring(14,13).Trim()
    >>             DeviceType = $_.Substring(27,15).Trim()
    >>             Shortform = $_.Substring(42,14).Trim()
    >>             DeviceDescription = $_.Substring(55,$_.Length - 55).Trim()
    >>         }
    >>     } |
    >>     Format-Table -AutoSize
    >>
    
    DeviceNumber DeviceDescription  Shortform DeviceType   DeviceStatus
    ------------ -----------------  --------- ----------   ------------
    2            Internal HDD       hdd       Hard Disk    Enabled
    1            Diskette Drive     floppy    Floppy Disk  Enabled
    3            USB Storage Device usbdev    USB device   Enabled
    4            CD/DVD/CD-RW Drive cdrom     CDRom        Enabled
    5            Onboard NIC        embnic    Embedded NIC Enabled
    
    
    PS D:\scripts>
    


    \_(ツ)_/

    Wednesday, June 27, 2018 5:06 PM
  • Ah, I see that object creation method results in an array the way I like it.

    Note: your code still pulls in all the help text output when you run "cctk.exe BootOrder". Your example sets a variable that already excludes that stuff. But, like I said already, the code I came up with solves that problem. Using the horizontal line counter ($HyphenLineCounter), it basically stops looping after it gets to the 2nd horizontal line and leaves all the undesired help information behind.

    Here's the updated code:

    [cmdletbinding()]
    Param(
        [string]$ToolLocation = ".\Utilities",
        [string]$ToolFileName = "cctk.exe"
    )
    
    Function Custom-Get-OSArchitecture
    {
        switch ((Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture)
        {
            '32-bit' { 'x86' }
            '64-bit' { 'x64' }
        }
    }
    
    Function Custom-Convert-DeviceRowToObject($Device)
    {
        New-Object System.Management.Automation.PSObject -property @{
    
            $HeaderRow.Substring(0,13).Trim() = $Device.Substring(0,13).Trim()
            $HeaderRow.Substring(14,13).Trim() = $Device.Substring(14,13).Trim()
            $HeaderRow.Substring(27,15).Trim() = $Device.Substring(27,15).Trim()
            $HeaderRow.Substring(42,14).Trim() = $Device.Substring(42,14).Trim()
            $HeaderRow.Substring(55,$HeaderRow.Length - 55).Trim() = $Device.Substring(55,$Device.Length - 55).Trim()
        }
    }
    
    [int]$HyphenLineCounter = 0
    [int]$LineCounter = 0
    [string]$OsArchitecture = Custom-Get-OSArchitecture
        
    [array]$BootDevices = Invoke-Expression "$ToolLocation\$OsArchitecture\$ToolFileName BootOrder" | ForEach-Object {
    
        If ($_ -match "-{3,}")
        {
            $HyphenLineCounter++
        }
        ElseIf (($HyphenLineCounter -lt 2) -and ($_.Trim()))
        {
            If ($LineCounter -eq 0)
            {
                [string]$HeaderRow = $_
            }
            Else
            {
                Custom-Convert-DeviceRowToObject $_
            }
    
            $LineCounter++
        }
    }
    
    If ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent)
    {
        $BootDevices | Format-Table -AutoSize
    }

    Yes, I'm still starting my function names with "Custom-" and I know it doesn't follow the PS convention. I do it so I don't accidentally step on a built-in PS cmdlet or function, though I realize in the specific cases in my code, that isn't likely.

    Input string (also includes a first line that is blank that I can't get to display on the forum):

     DeviceStatus DeviceNumber     DeviceType    Shortform  DeviceDescription
     -------------------------------------------------------------------------
      Enabled         2             Hard Disk          hdd  Internal HDD
      Enabled         1           Floppy Disk       floppy  Diskette Drive
      Enabled         3            USB device       usbdev  USB Storage Device
      Enabled         4                 CDRom        cdrom  CD/DVD/CD-RW Drive
      Enabled         5          Embedded NIC       embnic  Onboard NIC
     -------------------------------------------------------------------------
    You can use DeviceNumber or shortform to set the boot order.
    Example1: cctk BootOrder --Sequence=2,1,3 --DisableDevice=1 --EnableDevice=2,3
    Example2: cctk BootOrder --Sequence=cdrom,hdd.2,hdd.1 --DisableDevice=hdd.2 --En
    ableDevice=cdrom,hdd.1
    In file operation, same command can write like BootOrder=+cdrom,-hdd.2,+hdd.1
    
    
    

    Output array ($BootDevices | Format-Table -Autosize):

    DeviceNumber DeviceDescription  Shortform DeviceType   DeviceStatus
    ------------ -----------------  --------- ----------   ------------
    2            Internal HDD       hdd       Hard Disk    Enabled
    1            Diskette Drive     floppy    Floppy Disk  Enabled
    3            USB Storage Device usbdev    USB device   Enabled
    4            CD/DVD/CD-RW Drive cdrom     CDRom        Enabled
    5            Onboard NIC        embnic    Embedded NIC Enabled

    Thanks again!







    Wednesday, June 27, 2018 5:41 PM
  • What help text.  I see no difference.


    \_(ツ)_/

    Wednesday, June 27, 2018 7:56 PM
  • The stuff that appears after the 2nd horizontal line in the cctk.exe BootOrder command output:

    You can use DeviceNumber or shortform to set the boot order.
    Example1: cctk BootOrder --Sequence=2,1,3 --DisableDevice=1 --EnableDevice=2,3
    Example2: cctk BootOrder --Sequence=cdrom,hdd.2,hdd.1 --DisableDevice=hdd.2 --En
    ableDevice=cdrom,hdd.1
    In file operation, same command can write like BootOrder=+cdrom,-hdd.2,+hdd.1

    Wednesday, June 27, 2018 9:17 PM
  • That does not exists in your sample output.

    \_(ツ)_/

    Wednesday, June 27, 2018 9:24 PM
  • I don't know what to say.  This information is absolutely in both my original post as well as my 2nd to last post (the one starting with "Ah, I see that object creation method results in an array the way I like it.").


    Screenshot of original post, highlighting output in question.


    Screenshot of another post containing the output, with the output in question hightlighted.


    Wednesday, June 27, 2018 9:36 PM
  • If you had posted your example correctly we could have seen that that text was  intended to be part of the output.

    Please take care to post code and examples correctly.

    This can be accounted for with two more simple tests during load.


    \_(ツ)_/

    Wednesday, June 27, 2018 9:40 PM
  • The information was right in front of you the entire time from the very first post.

    Like I said, with the tips from both BOfH_666 and you, I now have working code that does exactly what I need. I require no further assistance.

    Feel free to get the last word in.

    Wednesday, June 27, 2018 9:49 PM