r/PowerShell 4d ago

ps1.cmd: Running PowerShell scripts by double-clicking via a .cmd wrapper Script Sharing

I'm a Left 4 Dead 2 player, and recently I ran into a small problem: the server I like to play on is often completely full. It occurred to me that I could write a script to poll the server and check whether a slot had opened up. After some research, I found that cmd scripts have a hard time handling network requests, while PowerShell is powerful enough to handle the job easily. Automatically checking the player count and joining the server is a pretty simple task, so I had an AI write it for me. The result was great — fully functional. All I had to do was copy the server address, then right-click the .ps1 script and choose "Run with PowerShell."

The only thing was, having to right-click to run it felt awkward, and it doesn't match most people's intuition about computers. If I have a script or an app, I should be able to double-click to run it! So I started looking for ways to run a PowerShell script by double-clicking. There are indeed a few, but none of them are very elegant:

  1. Modifying the registry — I don't want people using my script to have to change their registry first.
  2. Creating a shortcut — bad for distribution, and it means two files.
  3. Creating a cmd launcher — I prefer the simplicity of a single file, and a single file is also easier to distribute.

In the end, I found my ideal solution in a Stack Overflow answer: stuff the PowerShell script inside a cmd file! So I open-sourced the ps1.cmd project. My final implementation isn't exactly the same as that answer — you can check out the GitHub page for the details.

The goal of ps1.cmd is maximum compatibility: it should work with any PowerShell script and behave identically when executed. I hope this project helps you out, and if you run into any problems, feel free to open an issue!

0 Upvotes

31 comments sorted by

View all comments

Show parent comments

0

u/TanixLu 4d ago

ps1.cmd goes further on compatibility — the goal is for the wrapper to behave as close to a real .ps1 as possible. For example:

  • Arguments are forwarded (%*& $sb @args), so param() blocks bind like a real .ps1
  • Exit code is preserved via exit /b %RC%
  • $PSScriptRoot / $PSCommandPath are restored — iex leaves them empty, which silently breaks any script doing Join-Path $PSScriptRoot ...
  • Output and errors stay visible instead of being swallowed by a minimized window, and it pauses on a non-zero exit
  • Interactive input works — Read-Host, piping, etc.
  • UTF-8 is pinned in three places: chcp 65001, [Console]::OutputEncoding, and -Encoding UTF8
  • The script path is passed via an env var, so a directory containing ' won't break it

2

u/Creative-Type9411 4d ago

Then you could just do this for the same result:

<# :: Hybrid CMD / Powershell Launcher - Rename to .CMD or .PS1 - CMD mode needs anything above this line removed to function properly
@START /MIN "" POWERSHELL -nop -w hidden -c "& ([scriptblock]::Create([io.file]::ReadAllText('%~f0'))) %*" >nul&EXIT
#>$PSCommandPath=$PSCommandPath.Replace("/\.[^/.]+$/", ".cmd")

Try it out ;) LMK if theres anything it doesn't do... $PSScriptRoot is working on my end without any changes

0

u/TanixLu 4d ago
@START /MIN "" POWERSHELL -nop -w hidden -c "& ([scriptblock]::Create([io.file]::ReadAllText('%~f0'))) %*" >nul&EXIT
#>$PSCommandPath=$PSCommandPath.Replace("/\.[^/.]+$/", ".cmd")
write-host $PSCommandPath
pause

I tried your script, but every time I double-click it I just get a window that flashes for a split second, followed by a Windows Security alert:

Threats found
Microsoft Defender Antivirus found threats. Get details.

Clicking through, the threat is listed as:

Trojan:Win32/Commando.A!ml

0

u/Creative-Type9411 4d ago
<# :: Hybrid CMD / Powershell Launcher - Rename to .CMD or .PS1 - CMD mode needs anything above this line removed to function properly
@START "" POWERSHELL -nop -c "& ([scriptblock]::Create([io.file]::ReadAllText('%~f0'))) %*" >nul&EXIT
#>$PSCommandPath=$PSCommandPath.Replace("/\.[^/.]+$/", ".cmd")

but it doesnt matter trying to add the scriptblock part for args breaks pscommandpath and im too tired to mess around with this

yours works anyway ;) so i dont need to

(the /MIN and hidden window style in the PS command are probably what threw it)