Home > Software design >  Function for PowerShell command with elevated rights and need to skip confirmation
Function for PowerShell command with elevated rights and need to skip confirmation

Time:08-24

To disable a device via PS the following code can be used (skipping the confirmation dialog), it needs to run in an elevated CLI:

Disable-PnpDevice -InstanceID "BTHENUM\DEV_123\1&23E&0&BLUETOOTHDEVICE_001123" -confirm:$false

I created a Function that should run the command on a new elevated CLI but for some reason, it does not work on the above command:

Function turnoff { Start-Process -FilePath powershell.exe -ArgumentList { Disable-PnpDevice -InstanceID "BTHENUM\DEV_123\1&23E&0&BLUETOOTHDEVICE_001123" -confirm:$false } -verb RunAs }

The new CLI briefly opens but closes again and the command is not executed. I haven't found the problem yet, any hints would be appreciated.

CodePudding user response:

tl;dr

  • Pass the command to be executed by the elevated powershell.exe as a single string to Start-Process -ArgumentList - do not use a script block ({ ... }).

  • Escape any " characters that are part of the command passed to powershell.exe's (implied) -Command (-c) parameter as \"

Read on for an explanation; solution is at the bottom.


As noted in the comments, you can get away with passing a script block ({ ... }) to Start-Process' -ArgumentList parameter, because it is automatically converted to a string (the parameter is [string[]]-typed and therefore accepts one or more (an array of) strings), but it is ill-advised:

  • It is conceptually problematic, because it may mislead you to think that you can use regular PowerShell syntax inside the script block and that you can reference variables in it.

  • In fact, the script block stringifies to its verbatim content (without the { and }, which means that variable values can not be referenced.


You should pass a single string to begin with:

  • Note: While passing an array of arguments may be conceptually appealing, a long-standing bug that won't be fixed in the interest of backward compatibility - see GitHub issue #5576 - makes it preferable to encode all arguments in a single string, using escaped embedded quoting as necessary.

  • If you need to embed variable references or expressions in the string, i.e. if you need string interpolation, choose an expandable (double-quoted) string ("..."), otherwise use a verbatim (single-quoted) string ('...')

    • To make dealing with embedded quotes easier, you can use the here-string variants of these string literals (as shown below).

The (possibly expanded) single string you pass to -ArgumentList is copied verbatim to the command line used behind the scenes for actual invocation, so you need to make sure that you satisfy the command-line syntax requirements of the target application.

Since you happen to be calling the Windows PowerShell CLI, powershell.exe, which by default interprets positional arguments as targeting the -Command (-c) parameter,[1] special quoting requirements apply:

  • During initial command-line parsing, PowerShell considers unescaped " characters to have syntactic function on the command line only, and strips them.

    • In order to pass " characters that should be retained as part of the PowerShell command to execute, you must escape them as \" (sic).
  • After stripping unescaped " and converting escaped ones (\") to verbatim ones ("), the resulting arguments are joined with spaces and the resulting string is then executed as PowerShell code.

To put it all together, using a verbatim here-string (@'<newline>...<newline>'@), since no expansion (string interpolation) is needed:

Function turnoff { 

  # Note the need to escape the embedded " as \" for the sake of powershell.exe
  Start-Process -Verb RunAs -FilePath powershell.exe -ArgumentList @'
    -c Disable-PnpDevice -InstanceID \"BTHENUM\DEV_123\1&23E&0&BLUETOOTHDEVICE_001123\" -confirm:$false
'@ # Note: Delimiter must be *at the very start* of the line.

}

Note:

  • For full robustness, namely to avoid potential whitespace normalization, the entire -c (-Command) argument should be enclosed in (syntactic, unescaped) "...". In the interest of brevity I've omitted that above, as it isn't strictly necessary here.

  • To troubleshoot the call, place -NoExit before -c in order to keep the elevated session open after the command finishes executing.


[1] Caveat: pwsh, the PowerShell (Core) 7 CLI, now defaults to -File, so you'll always need to use -Command or -c explicitly there to pass commands.

  • Related