2

I have a ps1 script running ffmpeg and I'm trying to prompt the trim start time and then run the ffmpeg command. I need to use or something akin to %~ in powershell.

So far I've tried to escape the % in the string suggested from another post (i.e. `%~), but to no avail. As well as Google omits the special characters from my search query so it hasn't brought me much information trying to find the counterpart of it in PS.

So my question is what can I use in PS in replace of %~1 and %~dnp?

Code:

$start = Read-Host -prompt "trim start (ex. 00:00:00)"

Start-Process ffmpeg -ArgumentList "-i `"%~1`" -ss $start `"%~dnp`""

Read-Host -Prompt "Press Enter to exit"
ErraticFox
  • 1,363
  • 1
  • 18
  • 30
  • I'll never understand why people gravitate to start-process. – js2010 Apr 02 '23 at 15:26
  • 1
    @js2010, I suspect several factors: (a) People with a .NET / C# background treating PowerShell like a C# dialect, missing out on PowerShell's _shell_ features, (b) the prevalence of such answers here, and (c) the `Start-Process` docs not providing clear guidance on when `Start-Process` is and isn't necessary/appropriate. This has been improved _somewhat_ (fairly recently), prompted by [GitHub docs issue #6239](https://github.com/MicrosoftDocs/PowerShell-Docs/issues/6239), but I think a dedicated `about_Native_calls` topic is needed: https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5152 – mklement0 Apr 03 '23 at 18:26

2 Answers2

1

The direct equivalent will probably be to use $args to access the arguments passed in. So you can do $args[0] to get the equivalent of %~1 for example.

The tilde ~ in %~1 removes surrounding quotes, which isn't something you need to worry about when reading from arguments or parameters in a PowerShell script.

Parameters

Instead of $args you may want to explicitly define parameters for your script, which you can do with a param() block at the top of the script. You can even take your start time as a parameter and then prompt if it's blank.

param(
    $Path,
    $Start,
    $Destination
)

if (-not $Start) {
    $Start = Read-Host -prompt "trim start (ex. 00:00:00)"
}

Start-Process ffmpeg -ArgumentList @(
    "-i"
    $Path
    "-ss"
    $Start
    $Destination
)

Read-Host -Prompt "Press Enter to exit"

To call:

<script>.ps1 -Path path/to/source -Destination /path/to/destination -Start 12:34:56

(if you leave -Start out, it will prompt)

Quick note that %~dnp by itself may not be valid. Was there supposed to be a number there? Or a letter if you were using it in a FOR loop? I'm going to ignore it for now and take a new parameter for the destination.

I've also changed the Start-Process invocation to use an array form for the arguments, since I feel this is cleaner, but you can change it back to one big string if you like.

Read more about advanced functions to see how you can make parameters mandatory (which will automatically prompt for them if they're missing), validate the values, set the types, etc.

References

mklement0
  • 382,024
  • 64
  • 607
  • 775
briantist
  • 45,546
  • 6
  • 82
  • 127
  • It was suppose to be `%~dnp1` and as for args, I'm not able to find anything like `%~1` or `%~dnp1`. I'm using PyWinContext to run this script. So I right click the file and run the command, so I'm not able to explicitly define the start and destination. That's why I need it to be dynamic. – ErraticFox Apr 02 '23 at 14:49
  • 2
    Nice, but re `Start-Process`: A [long-standing bug](https://github.com/PowerShell/PowerShell/issues/5576) unfortunately requires use of _embedded_ double-quoting around arguments that contain spaces, e.g. `-ArgumentList '-foo', '"bar baz"'`. It is therefore generally better to encode all arguments in a _single string_, e.g. `-ArgumentList '-foo "bar baz"`, because the situational need to use embedded double-quoting is then obvious. See [this answer](https://stackoverflow.com/a/71271756/45375) for details. – mklement0 Apr 02 '23 at 14:52
  • 1
    @ErraticFox, do you mean `%dpn1` (that is, the `d`rive, `p`ath (without drive space), and `n`ame (without extension) parts of the full path of the path passed via `%1`? or did you mean `%dpn0`, i.e. to do this for `%0`, the path of the running batch file? – mklement0 Apr 02 '23 at 14:56
  • 1
    Thanks for the clarification @mklement0 ! As always please feel free to edit, or post an additional answer :) – briantist Apr 02 '23 at 15:31
  • @mklement0 I'm just trying to get the path of the file selected via right-click context menu. I tried to do (this is ran through a bat file, because the program only takes bat files) powershell "path/to/file.ps1 -filePath %~1` but that doesn't work. – ErraticFox Apr 02 '23 at 15:38
  • 1
    Thanks, Brian. @ErraticFox, invocation via a context menu always passes the full path - do you need the extension omitted (`%dpn1`)? If you're calling via a batch file, use the CLI's `-File` parameter and use `%1` as-is: `powershell.exe -file "path/to/file.ps1 -filePath %1`, and you can also pass `"%dpn1"`. The equivalent of `%~1` on the PowerShell side is `$args[0]`, as already mentioned in this answer. The equivalent of `%dpn1` is `((Convert-Path -LiteralPath $args[0]) -replace '^\.[^.]*$')` – mklement0 Apr 02 '23 at 16:08
  • If I just make my ps1 script do `Read-Host args[0]` after running it from the context menu, it displays a blank string. – ErraticFox Apr 02 '23 at 20:10
  • @ErraticFox, I assume you meant `Write-Host`, not `Read-Host`, and `$args[0]`, not `args[0]` (just using `$args[0]` by itself _implicitly_ outputs its value). However, I realized that the invocation command line for the shortcut-menu definition in the registry in my answer was flawed (since corrected), and should be `powershell -File "C:\path\to\your\script.ps1" "%V"`. If you must call via an intermediate batch file, use `"C:\path\to\your\batch-file.cmd" "%V"` and, from inside that batch file, call `powershell -File "C:\path\to\your\script.ps1" %1` – mklement0 Apr 04 '23 at 13:38
1

To complement briantist's helpful answer:

tl;dr

  • Don't use Start-Process to launch external console applications, unless you explicitly want them to run in a separate window or as a different user - see this answer for background information and GitHub docs issue #6239 for guidance on when use of Start-Process is and isn't appropriate.

    • To make a Start-Process call synchronous, i.e. to wait for it to complete, add the -Wait switch.

    • If, as is typical, Start-Process is not needed, use direct invocation, as shown below (if the executable name or path is quoted or contains variable reference, place &, the call operator before it).

  • %~1 and %~dpn1 (corrected from your question) only work in batch files, not also in PowerShell scripts (see bottom section for details).

  • You later stated that your script is called from a registry-based shortcut-menu command definition. The %1 (or %V) placeholder is always the full path of the file-system item that the menu command is being invoked from.
    Define the command line to place in the registry as follows:

    powershell -File "C:\path\to\your\script.ps1" "%V"
    
    • Note: As an alternative to using Read-Host -Prompt "Press Enter to exit" to prevent the window from closing after completion, you could place -NoExit before -File, which keeps the session alive and therefore the window open after the script file passed to -File has exited.

Then redefine your PowerShell script as ($args[0] is the PowerShell equivalent of cmd.exe's %~1):

$start = Read-Host -prompt "trim start (ex. 00:00:00)"

# Direct, synchronous invocation.
# Pass the first positional argument ($args[0]) both as-is and
# without its filename extension (invocation via a shortcut-menu command
# definition ensures that it is a full path).
ffmpeg -i $args[0] -ss $start ($args[0] -replace '\.[^.]*$')

Read-Host -Prompt "Press Enter to exit"

Background information on argument-passing and -expansion:

  • In command-line-based shortcut-menu definitions stored in the registry, %1 is a placeholder for the full path of the file-system item on which the shortcut menu was invoked (other forms are %L, and %V, with the latter being the only form that also works for background shortcut-menu definitions, i.e. shortcut-menu items that act on clicking in the background of a File Explorer window, implicitly targeting the directory).

    • Double-quoting is not automatically performed, so the safe form to use is "%1" (or, more universally, "%V"), i.e. to explicitly enclose the placeholder in "..." as part of the command line stored in the registry.
  • In batch files (*.cmd, .bat), which are interpreted by cmd.exe, %1 refers to the first argument that was passed on invocation - which may or may not be a file-system path, and which may or may not be a full path (%2 refers to the second argument, ..., and %0 refers to the batch file at hand, reflecting the path as invoked).

    • If the argument was passed enclosed in "...", %1 includes the double-quotes, but you can use %~1 to remove them.

    • Additionally, in arguments that are file-system paths ~ can be followed by a (combination of) letters that represents components of the path, notably d for the drive spec. (e.g., d:), p for the full, drive-relative path, ending in \ (e.g, \Windows\System32\), n for the base file name (i.e. the file name without extension (e.g., foo), and x for the extension (e.g., .txt) - run cmd /c call /? for details and additional letters.

      • As with use of ~ alone, the resulting string has any enclosing double quotes removed.

      • E.g, if "one two.txt" is passed as the first argument and the current directory is C:\path\to, %~dpn1 expands to verbatim C:\path\to\one two.

  • In PowerShell scripts (*.ps1):

    • The automatic $args variable contains the (positional) arguments that were passed as an array, and its elements never contain any (unescaped) enclosing "..." passed on the command line.

      • Thus, PowerShell's $args[0] is the equivalent of cmd.exe's %~1 (note the use of index 0 to refer to the first element, given that PowerShell is built on .NET, whose array indices are 0-based).

        • Note that PowerShell also supports named arguments, which are based on formally declared parameters, which is preferable for robustness - see briantist's answer.
    • cmd.exe-style argument expansions such as %~dpn1 have no direct PowerShell equivalent - you must use PowerShell's cmdlets and operators instead.

      • Assuming that the first argument refers to an existing file, the equivalent command is:

        # Convert to a full, file-system-native path and remove the extension.
        (Convert-Path -LiteralPath $args[0]) -replace '\.[^.]*$'
        
      • Sadly, converting a non-existent file-system path (such as a file that is about to be created) to a full, file-system-native one is more complicated, given that Convert-Path, as of PowerShell 7.3.3, only supports existing paths (see this answer for background information):

        # Windows PowerShell
        [IO.Path]::GetFullPath([IO.Path]::Combine($PWD.ProviderPath, $args[0]))
        
        # PowerShell (Core) 7+ (simpler)
        [IO.Path]::GetFullPath($args[0], $PWD.ProviderPath)
        
    • Similarly, there's no direct equivalent to cmd.exe's %0 to refer to the running batch file / script itself, but there are two automatic variables:

      • $PSScriptRoot contains the full path of the directory in which the running script is located.
      • $PSCommandPath contains the full path of the running script itself.
mklement0
  • 382,024
  • 64
  • 607
  • 775