Powershell script to get the metadata field "writing application" - powershell

I am using a modified version of the GetMetaData script originally written by Ed Wilson at Microsoft (https://devblogs.microsoft.com/scripting/hey-scripting-guy-how-can-i-find-files-metadata/) and then modified by user wOxxOm here https://stackoverflow.com/a/42933461/5061596 . I'm trying to analyze all my DVD and BluRay rips and see what tool was used to create them. Mainly I want to check which ones I compressed with Handbrake and which ones came directly from MakeMKV. The problem is I can't find this field.
If I use the "stock" scrip and change the number of properties it looks for from 0 - 266 up to 0 - 330 I find the extra file info like movie length, resolution, etc. But I can't find the tool used. For example here is what the MediaInfo Lite tool reports:
But looking through the meta data I get something like this with no "Writing application" property:
Name : Ad Astra (2019).mkv
Size : 44.1 GB
Title : Ad Astra
Length : 02:03:02
Frame height : 2160
Frame rate : ‎23.98 frames/second
Frame width : 3840
Total bitrate : ‎51415kbps
Audio tracks : TrueHD S24 7.1 [Eng]
Contains chapters : Yes
Subtitle tracks : PGS [Eng], PGS [Eng]
Video tracks : HEVC (H265 Main 10 #L5.1)
How do I go about finding that property or is it not something that I can pull through PowerShell?
Edit: The info I'm looking for IS in Windows Explorer looking at the properties of the file and the details tab so if Explorer can see it I would think I should be able to:

edit: actually, this seems more reliable. So far any file that mediainfo can read, this also works with.
$FILE = "C:\test.mkv"
$content = (Get-Content -Path $FILE -First 100) + (Get-Content -Path $FILE -Tail 100)
if(($content -match '\*data')[0] -match '\*data\W*([\w\n\s\.]*)'){
write-host "Writing Application:" $Matches[1]
exit
}elseif(($content -match 'M€.*WA(.*)s¤')[0] -match 'M€.*WA(.*)s¤'){
write-host "Writing Application:" $Matches[1]
}
It looks like the last bytes in the file after *data that specify the writer, so try this:
(Get-Content -Path "c:\video.mkv" -Tail 1) -match '\*data\W*(.*)$' | out-null
write-host "Writing Application:" $Matches[1]
On my test file that resulted in "HandBrake 1.5.1 2022011000"
I'm not sure what standard specifies this sorry. There's also a host of useful info on the first line of data in the file as well, e.g:
ftypmp42 mp42iso2avc1mp41 free6dÊmdat ôÿÿðÜEé½æÙH·–,Ø Ù#îïx264 - core 164 r3065 ae03d92 - H.264/MPEG-4 AVC codec - Copyleft 2003-2021 - http://www.videolan.org/x264.html - options: cabac=1 ref=1 deblock=1:0:0 analyse=0x1:0x111 me=hex subme=2 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=0 cqm=0 deadz
one=21,11 fast_pskip=1 chroma_qp_offset=0 threads=18 lookahead_threads=5 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=1 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=10 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin
=0 qpmax=69 qpstep=4 vbv_maxrate=14000 vbv_bufsize=14000 crf_max=0.0 nal_hrd=none filler=0 ip_ratio=1.40 aq=1:1.00
I couldn't replicate your success viewing the info with Windows Explorer, the field is invisible for me even though I can view it with MediaInfo etc

Related

Advice on how to compare string in a text file

Could anyone offer any advice how to compare string from a text file to the info pulled from wmi.
$GCard = (Get-WmiObject "Win32_Videocontroller").Name
$DriverModel = Get-Content -Path "C:\temp\Quadro473.81.txt"
foreach ($Line in $DriverModel)
{
if($Line -like "*$MyCard*")
{
write-host "Quadro"
}
}
The text file (Quadro473.81.txt) is as following.
UDA Package name: QuadroWeb Public Disk1
{"NSD":{"State":"0"},"DCH":{"State":"1"}}
nv_disp.inf:
DEV_0FF3 "NVIDIA Quadro K420"
DEV_0FF9 "NVIDIA Quadro K2000D"
DEV_0FFA "NVIDIA Quadro K600"
DEV_0FFD "NVIDIA NVS 510"
DEV_0FFE "NVIDIA Quadro K2000"
DEV_0FFF "NVIDIA Quadro 410"
DEV_1021 "NVIDIA Tesla K20Xm"
DEV_1022 "NVIDIA Tesla K20c"
DEV_1023 "NVIDIA Tesla K40m"
DEV_1024 "NVIDIA Tesla K40c"
DEV_1026 "NVIDIA Tesla K20s"
DEV_1027 "NVIDIA Tesla K40st"
DEV_1028 "NVIDIA Tesla K20m"
DEV_1029 "NVIDIA Tesla K40s"
The card in the machine report back as NVIDIA Quadro K420
I am trying to work out how to match the string from the video controller name to the list from the drivermodels and have it output the word quadro if it matches.
I would just need to match the model that appears in quotes.
Any help would be appriciated.
You can use -match to search through the array of strings returned by Get-Content and have it output "Quadro" when found inside your if statement.
$GCard = (Get-WmiObject "Win32_Videocontroller").Name
$DriverModel = Get-Content -Path "C:\temp\Quadro473.81.txt"
foreach ($GCName in $GCard)
{
if ($DriverModel -match $GCName)
{
Write-Host -Object "Quadro"
}
}
Few things to cover when using -match comparison operator.
When used against an array, it will act as a filter and return the line that was matched against $GCard.
It's underlying search pattern it uses are regular expressions.
So, if your call to (Get-WmiObject "Win32_Videocontroller").Name returned a value that has special characters, they would have to be escaped.
Since it returns the line it matches, it will evaluate the condition to "true" executing your block of code.
EDIT: Given that there could be multiple return values in $GCard, using a loop you can compare each line with $DriverModel.

Edit column in Tab-delimited Text file using Powershell

I have a very large (~250k row and 171 Column) Tab delimited text file that I need to edit. I need to add the letter "H" to the third column on every row.
So I need it to go from 03/20/2020 09:00 03/20/2020 10:00 1269805 ......
to 03/20/2020 09:00 03/20/2020 10:00 H1269805 .....
I actually have this working with the following code:
$source = Get-ChildItem "C:\test\input\*.txt"
$target = "C:\test\test.txt"
$data = Get-Content -Path $source | ConvertFrom-Csv -Delimiter "`t" -Header Column1, Column2, Column3, Column4, Column5, Column6, Column7, Column8, Column9, Column10, Column11, Column12, Column13, Column14, Column15, Column16, Column17, Column18, Column19, Column20,
Column21, Column22, Column23, Column24, Column25, Column26, Column27, Column28, Column29, Column30, Column31, Column32, Column33, Column34, Column35, Column36, Column37, Column38, Column39, Column40,
Column41, Column42, Column43, Column44, Column45, Column46, Column47, Column48, Column49, Column50, Column51, Column52, Column53, Column54, Column55, Column56, Column57, Column58, Column59, Column60,
Column61, Column62, Column63, Column64, Column65, Column66, Column67, Column68, Column69, Column70, Column71, Column72, Column73, Column74, Column75, Column76, Column77, Column78, Column79, Column80,
Column81, Column82, Column83, Column84, Column85, Column86, Column87, Column88, Column89, Column90, Column91, Column92, Column93, Column94, Column95, Column96, Column97, Column98, Column99, Column100,
Column101, Column102, Column103, Column104, Column105, Column106, Column107, Column108, Column109, Column110, Column111, Column112, Column113, Column114, Column115, Column116, Column117, Column118, Column119, Column120,
Column121, Column122, Column123, Column124, Column125, Column126, Column127, Column128, Column129, Column130, Column131, Column132, Column133, Column134, Column135, Column136, Column137, Column138, Column139, Column140,
Column141, Column142, Column143, Column144, Column145, Column146, Column147, Column148, Column149, Column150, Column151, Column152, Column153, Column154, Column155, Column156, Column157, Column158, Column159, Column160,
Column161, Column162, Column163, Column164, Column165, Column166, Column167, Column168, Column169, Column170, Column171
$data | % {
If ($_.Column3) {
#import ID
$_.Column3 = "H$($_.Column3)"
} }
$data | Select Column1, Column2, Column3, Column4, Column5, Column6, Column7, Column8, Column9, Column10, Column11, Column12, Column13, Column14, Column15, Column16, Column17, Column18, Column19, Column20,
Column21, Column22, Column23, Column24, Column25, Column26, Column27, Column28, Column29, Column30, Column31, Column32, Column33, Column34, Column35, Column36, Column37, Column38, Column39, Column40,
Column41, Column42, Column43, Column44, Column45, Column46, Column47, Column48, Column49, Column50, Column51, Column52, Column53, Column54, Column55, Column56, Column57, Column58, Column59, Column60,
Column61, Column62, Column63, Column64, Column65, Column66, Column67, Column68, Column69, Column70, Column71, Column72, Column73, Column74, Column75, Column76, Column77, Column78, Column79, Column80,
Column81, Column82, Column83, Column84, Column85, Column86, Column87, Column88, Column89, Column90, Column91, Column92, Column93, Column94, Column95, Column96, Column97, Column98, Column99, Column100,
Column101, Column102, Column103, Column104, Column105, Column106, Column107, Column108, Column109, Column110, Column111, Column112, Column113, Column114, Column115, Column116, Column117, Column118, Column119, Column120,
Column121, Column122, Column123, Column124, Column125, Column126, Column127, Column128, Column129, Column130, Column131, Column132, Column133, Column134, Column135, Column136, Column137, Column138, Column139, Column140,
Column141, Column142, Column143, Column144, Column145, Column146, Column147, Column148, Column149, Column150, Column151, Column152, Column153, Column154, Column155, Column156, Column157, Column158, Column159, Column160,
Column161, Column162, Column163, Column164, Column165, Column166, Column167, Column168, Column169, Column170, Column171 | ConvertTo-Csv -Delimiter "`t" -NoTypeInformation | % { $_ -replace '"', "" } | Select-Object -Skip 1 | Set-Content -Path $target
The problem I have is it takes a long time. I understand it is a large file, but is there any other way to do this faster? I feel like the converting to and from CSV is what is taking the longest, but I may be wrong. The whole process takes roughly 25 minutes to complete. Any help would be great.
To speed up processing, avoid the pipeline, use .NET types for file I/O and use plain-text operations:
# Create the output file.
$outFile = [IO.File]::CreateText($target)
# Loop over all input files
foreach ($file in Get-ChildItem C:\test\input\*.txt) {
# Loop over a given file's lines.
foreach ($line in [IO.File]::ReadLines($file.FullName)) {
# Prepend 'H' to the 3rd column and append to the output file.
$outFile.WriteLine(($line -replace '^.*?\t.*?\t', '$&H'))
}
}
$outFile.Close()
Note:
Be sure to always pass full file paths to .NET methods, because .NET's working directory usually differs from PowerShell's.
.NET file I/O methods default to BOM-less UTF-8 encoding.
The H is inserted in front of the 3rd tab-separated column using PowerShell's regex-based -replace operator.

Read in a *HUGE* CSV file, export specific columns based on column name in header, output comma delimited data

So, I know I can read in a csv file using import-csv like so:
$test = import-csv BPUSAUV20FARS-1000.csv
I found another stack overflow question which gave me some code to decipher column names, like so:
$columns = $test[0].psobject.properties.name
I found a reddit post that helped me find a way to extract multiple columns using select-object like so:
$properties = #('Index', 'Year', 'Day', 'Time', 'Line', 'Beam', 'Pos TPU', 'Depth TPU', 'Status')
$test |Select-Object $properties
But the output from the above command likes like this:
Index : 1
Year : EM2040-0073-1000-20200224-222235
Day : 25
Time : Accept
Line : 0.648
Beam : 24-FEB-2020:22:22:34.98
Pos TPU : 4.617
Depth TPU : 1124834.70
Status : 10247261.01
Index : 2
Year : EM2040-0073-1000-20200224-222235
Day : 26
Time : Accept
Line : 0.749
Beam : 24-FEB-2020:22:22:34.98
Pos TPU : 4.617
Depth TPU : 1124834.73
Status : 10247261.76
Index : 3
Year : EM2040-0073-1000-20200224-222235
Day : 27
Time : Accept
Line : 0.624
Beam : 24-FEB-2020:22:22:34.98
Pos TPU : 4.617
Depth TPU : 1124834.76
Status : 10247263.05
And what I need is this:
1,EM2040-0073-1000-20200224-222235,25,Accept,0.648,24-FEB-2020:22:22:34.98,4.617,1124834.70,10247261.01
I also need to be able to perform these actions on a few hundred files with several million lines each. The smallest file is about 2.4 million lines.
As for...
I also need to be able to perform these actions on a few hundred files
with several million lines each. The smallest file is about 2.4
million lines.
... dealing with large files in general --- (too long for just a comment)
As per MSDN
[IO.File]::OpenText and as noted in another Q&A
The Get-Content cmdlet does not perform as well as a StreamReader when
dealing with very large files. You can read a file line by line using
a StreamReader like this:
$path = 'C:\A-Very-Large-File.txt'
$r = [IO.File]::OpenText($path)
while ($r.Peek() -ge 0) {
$line = $r.ReadLine()
# Process $line here...
}
$r.Dispose()
Some performance comparisons:
Measure-Command {Get-Content .\512MB.txt > $null}
Total Seconds: 49.4742533
Measure-Command {
$r = [IO.File]::OpenText('512MB.txt')
while ($r.Peek() -ge 0) {
$r.ReadLine() > $null
}
$r.Dispose()
}
Total Seconds: 27.666803

Matching 'Network Location' icons

I am using the following code to create a Network Location, but the result is not exactly the same as when doing it manually. The icon from shell32.dll is smaller, lower res and has a frame, as seen here on the left. Is there any way to match the "native" look with PowerShell?
$linkFolder = New-Item -name:$location.name -path:"$nethoodPath\$($location.name)" -type:Directory -errorAction:stop
# Create the ini file
$desktopIniContent = (
'[.ShellClassInfo]',
'CLSID2={0AFACED1-E828-11D1-9187-B532F1E9575D}',
'Flags=2',
'ConfirmFileOp=1'
) -join "`r`n"
$desktopIniContent | Out-File -filePath:"$nethoodPath\$($location.name)\Desktop.ini"
# Create the shortcut file
$link = $shell.Createshortcut("$nethoodPath\$($location.name)\target.lnk")
$link.TargetPath = $location.value
$link.IconLocation = "%SystemRoot%\system32\SHELL32.DLL, 275"
$link.Description = $location.value
$link.WorkingDirectory = $location.value
$link.Save()
# Set attributes on the files & folders
Set-ItemProperty "$nethoodPath\$($location.name)\Desktop.ini" -name:Attributes -value:([IO.FileAttributes]::System -bxor [IO.FileAttributes]::Hidden) -errorAction:stop
Set-ItemProperty "$nethoodPath\$($location.name)" -name:Attributes -value:([IO.FileAttributes]::ReadOnly) -errorAction:stop
The icon on the right hand side can be found in imageres.dll (the Windows image resource library), icon 138 (index 137):
$link.IconLocation = "%SystemRoot%\system32\imageres,137"
It would seem that the horrible resolution of the shell32.dll,275 version is a bug/blunder that has been fixed by Microsoft subsequently:
Windows 7:
The shell32.dll version scales horribly
Windows 10:
On Windows 10, shell32.dll,275 scales to "Extra Large Icons"-size perfectly fine

powershell and console app output

Im trying to automate video conversion with powershell and ffmpeg tool.
Ffmpeg have detailed output about video if called without all nessesary parameters. Usually it reports about error and display input file info if one specified.
Ex I interactively executed such command:
d:\video.Enc\ffmpeg.exe -i d:\video.Enc\1.wmv
this is powershell console output
ffmpeg.exe : FFmpeg version SVN-r20428, Copyright (c) 2000-2009 Fabrice Bellard, et al.
row:1 char:24
+ d:\video.Enc\ffmpeg.exe <<<< -i d:\video.Enc\1.wmv
+ CategoryInfo : NotSpecified: (FFmpeg version ...Bel
lard, et al.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
built on Nov 1 2009 04:03:50 with gcc 4.2.4
configuration: --enable-memalign-hack --prefix=/mingw --cross-pre
fix=i686-mingw32- --cc=ccache-i686-mingw32-gcc --target-os=mingw32
--arch=i686 --cpu=i686 --enable-avisynth --enable-gpl --enable-vers
ion3 --enable-zlib --enable-bzlib --enable-libgsm --enable-libfaad
--enable-pthreads --enable-libvorbis --enable-libtheora --enable-li
bspeex --enable-libmp3lame --enable-libopenjpeg --enable-libxvid --
enable-libschroedinger --enable-libx264 --enable-libopencore_amrwb
--enable-libopencore_amrnb
libavutil 50. 3. 0 / 50. 3. 0
libavcodec 52.37. 1 / 52.37. 1
libavformat 52.39. 2 / 52.39. 2
libavdevice 52. 2. 0 / 52. 2. 0
libswscale 0. 7. 1 / 0. 7. 1
[wmv3 # 0x144dc00]Extra data: 8 bits left, value: 0
Seems stream 1 codec frame rate differs from container frame rate:
1000.00 (1000/1) -> 15.00 (15/1)
Input #0, asf, from 'd:\video.Enc\1.wmv':
Duration: 00:12:0
2.00, start: 5.000000, bitrate: 197 kb/s
Stream #0.0(eng): Audio: wmav2, 44100 Hz, 1 channels, s16, 48 k
b/s
Stream #0.1(eng): Video: wmv3, yuv420p, 1024x768, 137 kb/s, 15 tbr, 1k tbn, 1k tbc Metadata
title : Silverlight 2.0 Hello World Application
author : Sergey Pugachev
copyright :
comment :
WMFSDKVersion : 11.0.6001.7000
WMFSDKNeeded : 0.0.0.0000
IsVBR : 1
ASFLeakyBucketPairs:
VBR Peak : 715351
Buffer Average : 127036
At least one output file must be specified
But I cant figure how to script this and capture output to any kind of posh objects.
I tried direct script, wher ps1 file contained exact expression "d:\video.Enc\ffmpeg.exe -i d:\video.Enc\1.wmv" - it didnt work. Also i tried to do that with invoke-command and invoke expression. First one returns an exact string with command, second one - dump error to output console but not to -ErrorVariable i specified ( I did set all out variables, not only error one - all of them were empty).
Can anyone point to correct syntax for invoking console applications in posh and capturing output ?
Second one question will be about parsing that output - I'll need video resolution data to calculate correct aspect ratio for conversion. So it will be cool if anyone point how to work with captured error output and parse string like
Stream #0.1(eng): Video: wmv3, yuv420p, 1024x768,
Try redirecting the error stream to stdout like so and then you should be able to capture both stdout and stderr in a single variable e.g.:
$res = d:\video.Enc\ffmpeg.exe -i d:\video.Enc\1.wmv 2>&1
To capture the data try this:
$res | Select-String '(?ims)^Stream.*?(\d{3,4}x\d{3,4})' -all |
%{$_.matches} | %{$_.Groups[1].Value}
I'm not sure if $res will be one string or multiple but the above should work for both cases.