asanidar
asanidar

Reputation: 15

Converting a line of cmd to powershell

EDIT2: Final code below

I need help on converting some codes as I am very new to mkvmerge, powershell and command prompt.

The CMD code is from https://github.com/Serede/mkvtoolnix-batch/blob/master/mkvtoolnix-batch.bat for %%f in (*.mkv) do %mkvmerge% @options.json -o "mkvmerge_out/%%f" "%%f"

What I've managed so far

$SourceFolder = "C:\tmp" #In my actual code, this is done using folder browser
$SourceFiles = Get-ChildItem -LiteralPath $SourceFolder -File -Include *.mkv
$SourceFiles | foreach
{
    start-process "F:\Desktop\@progs\mkvtoolnix\mkvmerge.exe"
}

I'd be grateful for any help as I'm having trouble understanding and converting while learning both sides. Thank you very much.

**EDIT 2:**Here's my final working code.

Function Get-Folder($initialDirectory) {

    #Prompt to choose source folder
    [void] [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
    $FolderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog
    $FolderBrowserDialog.Description = 'Choose the video folder'
    $FolderBrowserDialog.RootFolder = 'MyComputer'
    if ($initialDirectory) { $FolderBrowserDialog.SelectedPath = $initialDirectory }
    [void] $FolderBrowserDialog.ShowDialog()
    return $FolderBrowserDialog.SelectedPath


}

Function ExitMessage 
{
#endregion Function output
Write-Host "`nOperation complete";
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
Exit;
}

($SourceFolder = Get-Folder  | select )

#Check for output folder and create if unavailable
$TestFile = "$SourceFolder" + "\mkvmerge_out"
if ((Test-Path -LiteralPath $TestFile) -like "False")
{
    new-item -Path $SourceFolder -name "mkvmerge_out" -type directory
    Write-Host 'Folder created';
}

#Checking for the presence of a Json file
$TestFile = (Get-ChildItem -LiteralPath $SourceFolder -File -Filter *.json)
if ($TestFile.count -eq 0)
{
    Write-Host 'json file not found';
    ExitMessage;
}
$TestFile = "$SourceFolder" + "\$TestFile"


#Getting the total number of files and start timer.
[Int] $TotalFiles = 0;
[Int] $FilesDone = 0;
$TotalFiles = (Get-ChildItem -LiteralPath $SourceFolder -File -Filter *.mkv).count
$PercentFiles = 0;
$Time = [System.Diagnostics.Stopwatch]::StartNew()

#Start mkvmerge process with progress bar
$mkvmergeExe = 'F:\Desktop\@progs\mkvtoolnix\mkvmerge.exe'
$JsonFile = "$TestFile" # alternatively, use Join-Path
Get-ChildItem -LiteralPath $SourceFolder -File -Filter *.mkv | ForEach-Object {
    $PercentFiles = [math]::truncate(($FilesDone/$TotalFiles)*100)
    Write-Progress -Activity mkvmerge -Status ("{0}% Completed; {1}/{2} done; Time Elapsed: {3:d2}:{4:d2}:{5:d2}" -f $PercentFiles, $FilesDone, $TotalFiles, $Time.Elapsed.Hours, $Time.Elapsed.minutes, $Time.Elapsed.seconds) -PercentComplete $PercentFiles;
    Write-Host "Processing $_"
    $f = $_.FullName
    $of = "$SourceFolder\mkvmerge_out\$($_.Name)"
    & $mkvmergeExe -q `@$JsonFile -o $of $f
    $FilesDone++
}

Remove-Item -LiteralPath $JsonFile #Remove this line if you want to keep the Json file
$PercentFiles = [math]::truncate(($FilesDone/$TotalFiles)*100)
Write-Progress -Activity mkvmerge -Status ("{0}% Completed; {1}/{2} done; Time Elapsed: {3:d2}:{4:d2}:{5:d2}" -f $PercentFiles, $FilesDone, $TotalFiles, $Time.Elapsed.Hours, $Time.Elapsed.minutes, $Time.Elapsed.seconds) -PercentComplete $PercentFiles;
ExitMessage;

Upvotes: 1

Views: 483

Answers (1)

mklement0
mklement0

Reputation: 437062

$mkvmergeExe = 'F:\Desktop\@progs\mkvtoolnix\mkvmerge.exe'
$optionsFile = "$SourceFolder\options.json" # alternatively, use Join-Path
Get-ChildItem -LiteralPath $SourceFolder -File -Filter *.mkv | ForEach-Object {
  $f = $_.FullName
  $of = "$SourceFolder\mkvmerge_out\$($_.Name)"
  & $mkvmergeExe `@$optionsFile -o $of $f
}

Note that your cmd code assumes that it's operating in the current directory, while your PowerShell code passes a directory explicitly via $SourceFolder; therefore, the options.json file must be looked for in $SourceFolder and too, and the output file path passed to -o must be prefixed with $SourceFolder too which is achieved via expandable strings ("...") .

The main points to consider:

  • for %%f in (*.mkv) has no direct counterpart in PowerShell; you correctly used Get-ChildItem instead, to get a list of matching files, which are returned as System.IO.FileInfo instances.

    • However, -Include won't work as intended in the absence of -Recurse (unless you append \* - see this GitHub issue; -Filter does, and is also the faster method, but it has its limitations and legacy quirks (see this answer).
  • While PowerShell too allows you to execute commands whose names or paths are stored in a variable (or specified as a quoted string literal), you then need &, the call operator, to invoke it, for syntactic reasons.

  • Inside a script block ({ ... }) passed to the ForEach-Object cmdlet, automatic variable $_ represents the pipeline input object at hand.

    • $_.FullName ensures that the System.IO.FileInfo input instances are represented by their full path when used in a string context.

    • This extra step is no longer necessary in PowerShell [Core] 6+, where System.IO.FileInfo instances thankfully always stringify as their full paths.

  • The @ character is preceded by ` (backtick), PowerShell's escape character, because @ - unlike in cmd - is a metacharacter, i.e. a character with special syntactic meaning. `@ ensures that the @ is treated verbatim, and therefore passed through to mkvmerge.

    • Alternatively, you could have quoted the argument instead of escaping just the @: "@$optionsFile"

    • See this answer for background information.

  • You generally do not need to enclose arguments in "..." in PowerShell, even if they contain spaces or other metacharacters.

Upvotes: 2

Related Questions