Search code examples
pythonpowershellcmdsubprocess

Run `ps1` scripts without specifying the executor


In Python, we can use subprocess to run bat (cmd) scripts natively, like

import subprocess as sp
sp.run(["D:/Temp/hello.bat"])

works fine. However, it cannot run ps1 scripts natively, codes like

import subprocess as sp
sp.run(["D:/Temp/hello.ps1"])

will cause `WinError 193: %1 is not a valid Win32 application" to be raised.

I thought it was because the .PS1 had not been showing up in PATHEXT and added it there, but it failed again. I also tried the way provided in this answer which seems to be setting the file execution policy and it still wouldn't work.

I know it would work when I add the executor in the call like sp.run(["pwsh", "D:/Temp/hello.ps1"]) or use a bat script as intermediate, but that's not desired. I am using a program which is badly ported from *nix to Windows and it just call whatever I provided as a single executable (on *nix we can use shebang, but not on windows).


Solution

    • Batch files are special in that they are the only type of interpreter-based script files directly recognized as executables by the system.

    • For any other script files, including PowerShell scripts (*.ps1), the only way you can achieve their execution when directly invoked is:

      • Redefine the command that is used by the Windows shell to open *.ps1 files (i.e. the command associated with the Open shell verb) so as to pass the file path at hand to the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7) for execution.

        • Note: The default behavior when opening *.ps1 files from outside PowerShell (which includes invoking them from cmd.exe and double-clicking in File Explorer) is to open them for editing. Changing the behavior to executing PowerShell scripts may be unexpected by other users, so it's best to limit this change to your own user account..

        • The SuperUser answer you link to shows how do to that, via the registry; note that the linked code requires elevation, i.e. running with administrative privileges.

          • However, given the above, you may prefer to define the command for the current user only, in which case elevation isn't required - see the second-to-last section of this answer.
      • In order for subprocess.run() to use the redefined command, shell=True must be passed (which calls via cmd.exe, which automatically invokes the default Windows shell operation on non-executable files):

        import subprocess as sp
        # Performs the "Open" Window shell operation on the *.ps1 file.
        sp.run(["D:/Temp/hello.ps1"], shell=True)
        
    • If you don't want to redefine the command associated with the Open shell verb, your only option is indeed to call *.ps1 files via the PowerShell CLI; e.g.:

      import subprocess as sp
      sp.run(["powershell.exe", "-NoProfile", "-File", "D:/Temp/hello.ps1"])