Display String Variable as Multiline but Single line output

Hello,

I’m familiar with the Line Continuation in PowerShell feature for keeping code from running far out of view.

Is there a way to use this method - or similar - to work with a long string variable? When I tried this, it would output the string as multiple lines instead of a single line.

I’m adding headers to a CSV file before I populate it with some metadata and file property values. I currently have 21 headers (for now), and so using a single variable, they run way off screen in the script editor. I wanted to see if there was a way to show them on multiple lines within the script, but only write them to the CSV file on a single line.

Here’s what my current string variable looks like:

$TemplateHeaders = "FileName,FileSize,Duration,Audio Count,Text Count,Title,Format,v BitRate,BitDepth,Standard,v StreamSize,a Language,a CodecID,a Duration,a BitRate,Channel(s),a StreamSize,t CodecID,t Duration,t StreamSize,t Language,"

These fall into four categories, and I wanted to work with them in the script on their own lines so it’s more readable.

I just tried the following to achieve the desired results, but was wondering if there is a more professional way:

Here I have each category seperated into their own strings:

$gHeader = "FileName,FileSize,Duration,Audio Count,Text Count,Title,Format"
$vHeader = "v BitRate,BitDepth,Standard,v StreamSize"
$aHeader = "a Language,a CodecID,a Duration,a BitRate,Channel(s),a StreamSize"
$tHeader = "t CodecID,t Duration,t StreamSize,t Language"

Having it displayed on multiple lines makes it easier to manage the headers (add, remove, etc.)

Here I’m combining them into a single string:

$TemplateHeaders = "$gHeader,$vHeader,$aHeader,$tHeader,"

Here I just write the column headers to a new (empty) csv file:

Add-Content -Path $OutFile -Value $TemplateHeaders

Is there a way to achieve the same single line output results, but work with the data in the script across multiple lines?

Thanks…

You could use a here-string and replace the line breaks:

$TemplateHeaders = @"
FileName,
FileSize,
Duration,
Audio Count,
Text Count,
Title,Format,v BitRate,BitDepth,
Standard,v StreamSize,a Language,
a CodecID,a Duration,
a BitRate,Channel(s),a StreamSize,
t CodecID,t Duration,t StreamSize,t Language
"@

$TemplateHeaders -replace "`r`n" | Add-Content -Path $OutFile

Or you could use an array of strings and join them with a comma:

$TemplateHeaders = @(
    'FileName'
    'FileSize'
    'Duration'
    'Audio Count'
    'Text Count'
    'Title'
    'Format'
    'v BitRate'
    'BitDepth'    
)

$TemplateHeaders -join ',' | Add-Content -Path $OutFile

Best option, of course, is to build custom objects and let PowerShell handle the export. Is there a reason you can’t take that approach and want to manually build the file?

2 Likes

Hi Matt,

I tested both of your suggestions and I’m loving the Here-String method. This one gives me a lot of flexibility. After looking into it, I see it can obviously do a lot more than I can even imagine. Hear, Hear for Here-Strings – PowerShell.org

The Join method works pretty good, too. I can see using that for smaller sets of data that are more readable in a list.

For the case of retrieving metadata from media files, the Here-String works perfectly. I’m able to group the data in their respective rows (categories) and then string them together before writing them to the CSV file.

$TemplateHeaders = @"
FileName,FileSize,Duration,Audio Count,Text Count,Title,Format,
v BitRate,BitDepth,Standard,v StreamSize,
a Language,a CodecID,a Duration,a BitRate,Channel(s),a StreamSize,
t CodecID,t Duration,t StreamSize,t Language
"@

Output is a single line and they are properly separated in the csv file:

$TemplateHeaders -replace "`r`n" | Set-Content -Path $OutFile
FileName,FileSize,Duration,Audio Count,Text Count,Title,Format,v BitRate,BitDepth,Standard,v StreamSize,a Language,a CodecID,a Duration,a BitRate,Channel(s),a StreamSize,t CodecID,t Duration,t StreamSize,t Language

When you say “manually build the file”, are you referring to my $TemplateHeaders to declare my column headers?

If so, my brief backstory is that I’m learning how to use MediaInfo (CLI). They have so many parameters that it’s difficult to discern them. Many are the same name, but fall into different categories: general, video, audio, text, other, image, and menu.

It requires the use of a Template - text file - in order to retrieve parameters from more than one category - which I’m doing in this case. Because of how it outputs its results, the data will never line up perfectly when it comes to the audio and text (subtitle) categories as they relate to movies and shows. This means I need to shift the row data to line up with my column headers, which is simple enough using Excel.

Using your recommended Here-String solution will make it easier for me to fine tune my column headers as I continue to learn how to use that cryptic program.

Matt, thanks again for all your expert help and insight with all things PowerShell…

In most cases I personally avoid here-strings unless I really need to preserve a huge block of text. Even still I can typically work around it. That’s because I often format my code but with here-strings the final "a needs to be on the start of the line. You can’t have a preceding space in front of it, it needs to be by itself on its own line w/ nothing in front of it/behind it. It’s one of the few times white space matters in PS.

Another option you can concatenate or add strings too and the addition operator acts as a natural line break

$String = 'This is a really long sentence ' +
'that I will add.'

Caveat to this one is that you need to make sure to include spaces where appropriate to make the string read write.

Yes, you’re just working with strings and building a text file line-by-line.

Here’s an (admittedly contrived) example using three categories of data for Bob.

$NameDetails = @{
    Firstname  = 'Bob'
    MiddleName = 'Up'
    Surname    = 'Andown' 
}

$AddressDetails = @{
    HouseNumber = 5
    Road        = 'High Road'
    City        = 'London'
    Country     = 'UK'
}

$ContactDetails = @{
    Phone  = '01234 567 890'
    Mobile = '07967 123 456'
    Email  = 'bob.upandown@onthewater.com'
}

[PSCustomObject] @{
    FirstName     = $NameDetails.Firstname
    Surname       = $NameDetails.Surname
    StreetAddress = "$($AddressDetails.HouseNumber) $($AddressDetails.Road)"
    Country       = $AddressDetails.Country
    Email         = $ContactDetails.Email
} | Export-Csv -Path 'E:\Temp\1.csv' -NoTypeInformation

I can take information from the different categories and build an object that has the properties I want.
I can then use Export-CSV and have PowerShell build the file for me, no messing about shifting columns around. If I decide the CSV file isn’t pretty enough, I could pipe the object to Export-Excel (part of the ImportExcel module) and do some fancy formatting instead.

Or, in your case, maybe you could pipe the object to a function and have it read the filename and launch a media player. Objects give you much more control, and much more flexibility.

3 Likes

Hi Matt,

I like what you’re proposing. I’ll need some time to figure out if this will work for my home media project considering I’m at the mercy of the MediaInfo program which is actually what’s extracting the metadata.

I think my issue is that this MediaInfo.exe program spits out information from each file without the ability to keep like parameters lined up. And I honestly don’t know how I could possibly force it to.

Here’s a comparison that might help visualize the mess it spits out using the following MediaInfo Template file:

Category;%Parameter1%,%Parameter2%, etc.

General;%FolderName%,%FileExtension%,%AudioCount%,%TextCount%,
Audio;%ID%,%Language/String%,
Text;%ID%,%Language/String%,

I excluded the Video category to get a smaller sample set.

This first image is the desired output; keeping all the parameters lined up when outputted by the MediaInfo program:
I’m using colors to show the Audio (blue) and Text (green) parameters. I lined these up myself.

Here we can see where the Audio and Text category parameters are not lined up.

You can see that Jumanji has 4 Audio streams (blue) and 9 Text (subtitle) streams. I truncated the subtitles to only three.
If we move down to Big, we have only 3 Audio streams and then the Text streams follow immediately; only 1 in this case.
We continue down to 2 Audio and then 1 Audio with their respective trailing Text parameters.

I’ll admit that this behavior is not a deal breaker for me for the time being. It’s taken me a long time just to get to this point, so I’m excited to have this jalopy of a program to get me down the street… even if it does have two flat tires.

Earlier I decided to extract the AudioCount value only for each of my movies. The maximum value was 4.

foreach($movie in $movies)
{
    $m1Array.Add((mediainfo --Inform="General;%AudioCount%" $movie)) 
}

$m1Array | Measure-Object -Maximum

Now I just need to focus on the ones with 4 audio streams where I plan to extract the ones I don’t want.
I just want 1: English Surround or Stereo… but only 1. This needs to be done using a different program of course…

More learning… :slight_smile:

Here’s an example of how you can take the output of mediainfo.exe and turn it into usable objects.

# store the executable path in a variable
$mediainfo = 'path\to\MediaInfo.exe'

# this list will be used to keep track of the varying properties. All objects need to have the same properties even if many are blank.
$propertylist = New-Object System.Collections.Generic.List[string]

# sudo code, replace with your command that collects the desired files to process
$filelist = Get-Childitem C:\path\to\folder\* -Include '*.avi','*.jpg','*.bmp' -Recurse

# collect all the output we generate in the loop into $data
$data = $filelist | ForEach-Object {
    # for each file we create a new empty hashtable for tracking/collection
    $ht = @{}

    # execute mediainfo.exe against the current file. We look for only the lines that contain a colon and replace the : with =
    # We also have to double up backslashes for ConvertFrom-StringData as it uses regular expressions.
    (& $mediainfo $_.FullName) -match '\s:\s' -replace '\s:\s','=' -replace '\\','\\' | ForEach-Object {

        # each line should be formatted like 'Some Property    = some value' which ConvertFrom-StringData will turn into a hashtable
        $current = $_ | ConvertFrom-StringData

        # defensive coding, just in case somehow more than one line got passed through at once
        if($current.Keys.count -gt 1){
            Write-Warning "More than one property being processed at the same time"
        }
        else{
            # The current "property" or "key"
            $key = $current.Keys -as [string]

            # if $ht already has this key, write a warning that it's a duplicate
            if($key -in $ht.Keys){
                Write-Warning "Property '$key' is duplicated"

                # if the existing property value does not already contain this duplicate value, we append it in parenthesis
                $value = $ht[$key]
                if(@($value) -notcontains $current[$key]){
                    $ht[$key] = "$value ($($current[$key]))"
                }
            }
            else{
                # $ht did not contain the current property so we add this current hashtable to the main tracking hashtable
                $ht += $current
            }
        }
    }
    
    # check all the current "properties" and add to the collective $propertylist if not present
    foreach($property in $ht.Keys){
        if($propertylist -notcontains $property){
            $propertylist.Add($property)
        }
    }

    # this is our output, it converts the hashtable to a PSCustomObject and outputs it. These are what is collected in $data
    [PSCustomObject]$ht
}

# $data currently contains possibly several different objects with varying properties. We use our collective property list to normalize all objects with all properties
$results =  $data | Select-Object $propertylist

Now that we have consistent objects in $results we can process it how we like.

# for our eyes
$results | Out-GridView
-or-
$results | Format-Table

# export to csv
$results | Export-Csv \path\to\file.csv -NoTypeInformation

# export to excel (using the wonderful ImportExcel module)
$results | Export-Excel \path\to\file.xlsx -AutoFilter -AutoFormat -BoldTopRow -FreezeTopRow -TableStyle Medium17
3 Likes

Hi @krzydoug

I just saw your post. Thanks a lot for taking the time to draw it up and include some helpful comments explaining what the code does.

I put this on my to-do list for tomorrow. I’ll start building the hash table to see what the data looks like.

I was thinking about what Matt suggested earlier, and it got me thinking that I should read my “PowerShell for Sys Admins” by Adam Bertram. It was the chapter on arrays and hash tables that got my attention; thinking that I might be able to grab the data and somehow work with it as he suggested… and you have outlined here.

I’m too exhausted to test it out tonight, so I’ll get back to you soon…

Thank again…

Hi krzydoug,

I just finished testing your code and it ran successfully after tailoring the following to run in my environment:

INPUT:

# store the executable path in a variable
$mediainfo = 'C:\Users\ZERO\Tools\MediaInfo\MediaInfo.exe'

# sudo code, replace with your command that collects the desired files to process
$filelist = Get-ChildItem -Path $pathMedia -Recurse -Include *.mp4, *.mkv, *.m2ts, *.m4v, *.avi -Exclude $excludeArray | % { $_.FullName }

OUTPUT:

# export to csv
$results | Export-Csv "D:\Movies 3D.csv" -NoTypeInformation

# export to excel (using the wonderful ImportExcel module)
$results | Export-Excel "D:\Movies 3D.xlsx" -AutoFilter -BoldTopRow -FreezeTopRow -TableStyle Medium17

Note: POSH rejected the -AutoFormat parameter for Export-Excel.

Metadata in general is a pain in the butt when it comes to media files… and more so with video files. It’s the way they are structured. This is also the reason I’ve put this off for so long.

Unfortunately, I’m unable to use the data because values from duplicate Keys are being combined. This is what caused me to have to create custom headers for only the parameters that I was extracting and then manually lining them up in Excel previously.

Using MediaInfo for this may be my main issue. I’ve been looking at two other programs that read metadata from media files: FFprobe (part of FFmpeg) and ExifTool.

I’ll play around with these options some more, then start a new thread once I get a clearer picture on the proper program to use for this metadata extraction process.

A parting thought… I may need to export the data to a json file first and then have PowerShell read it in.

$inputFile = "Aquaman (2018) {edition-3D}.mp4"
$outputFile = "Aquaman (2018) {edition-3D}.json"

mediainfo $inputFile --Output=JSON -f >> $outputFile
$Metadata = Get-Content $outputFile

Then I’ll play around with narrowing everything down once I get the right Keys (parameters) to use… which is dependent on the program I use.

Thanks again, Doug and best regards,

My apologies, it should be -AutoSize

This was a decision I made. If you don’t desire that, change it to suit your needs. I don’t know what you would want to rename duplicates to, so I’ve just added_V2 to those that are duplicates.

# store the executable path in a variable
$mediainfo = 'path\to\MediaInfo.exe'

# this list will be used to keep track of the varying properties. All objects need to have the same properties even if many are blank.
$propertylist = New-Object System.Collections.Generic.List[string]

# sudo code, replace with your command that collects the desired files to process
$filelist = Get-Childitem '\path\to\files' -Include '*.avi','*.jpg','*.bmp' -Recurse

# collect all the output we generate in the loop into $data
$data = $filelist | ForEach-Object {
    # for each file we create a new empty hashtable for tracking/collection
    $ht = [ordered]@{}

    # execute mediainfo.exe against the current file. We look for only the lines that contain a colon and replace the : with =
    # We also have to double up backslashes for ConvertFrom-StringData as it uses regular expressions.
    (& $mediainfo $_.FullName) -match '\s:\s' -replace '\s:\s','=' -replace '\\','\\' | ForEach-Object {

        # each line should be formatted like 'Some Property    = some value' which ConvertFrom-StringData will turn into a hashtable
        $current = $_ | ConvertFrom-StringData

        # defensive coding, just in case somehow more than one line got passed through at once
        if($current.Keys.count -gt 1){
            Write-Warning "More than one property being processed at the same time"
        }
        else{
            # The current "property" or "key"
            $key = $current.Keys -as [string]

            # if $ht already has this key, process as desired
            if($key -in $ht.Keys){
                # Append the duplicate key with _V2
                $newkey = "$key{0}" -f "_V2"
                $ht[$newkey] = $current[$key]
            }
            else{
                # $ht did not contain the current property so we add this current hashtable to the main tracking hashtable
                $ht += $current
            }
        }
    }
    
    # check all the current "properties" and add to the collective $propertylist if not present
    foreach($property in $ht.Keys){
        if($propertylist -notcontains $property){
            $propertylist.Add($property)
        }
    }

    # this is our output, it converts the hashtable to a PSCustomObject and outputs it. These are what is collected in $data
    [PSCustomObject]$ht
}

# $data currently contains possibly several different objects with varying properties. We use our collective property list to normalize all objects with all properties
$results =  $data | Select-Object $propertylist

# for our eyes
$results | Out-GridView
# -or-
$results | Format-Table

# export to csv
$results | Export-Csv \path\to\file.csv -NoTypeInformation

# export to excel (using the wonderful ImportExcel module)
$results | Export-Excel \path\to\file.xlsx -AutoFilter -AutoSize -BoldTopRow -FreezeTopRow -TableStyle Medium17
1 Like

Hi krzydoug,

That certainly made the data easier to discern. It’s a ton of metadata, so I was trying to get that program work with the template file to only pull the parameters that I wanted… but it wasn’t playing nice.

I’ll play around with it some more later on. Right now I’m trying to compare differences between using mediainfo, ffprobe and exiftool.

I’ll reach out again later and create a new thread if I run into any further POSH related issues on my end.

Thanks again Doug… :+1:t3: