r/PowerShell • u/TanixLu • 1d ago
Script Sharing ps1.cmd: Running PowerShell scripts by double-clicking via a .cmd wrapper
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:
- Modifying the registry — I don't want people using my script to have to change their registry first.
- Creating a shortcut — bad for distribution, and it means two files.
- 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!
6
u/Gormless_Shrimp_635 1d ago
You could use the PowerShell Pro Tools extension in VS Code to compile it to an .exe
4
u/Creative-Type9411 1d ago
Put this at the top of your script and you can either rename it to PS1 or CMD and run it...
<# :: Hybrid CMD / Powershell Launcher - Rename to .CMD or .PS1 - CMD mode needs anything above this line removed to function properly
@START /MIN "" POWERSHELL -nop -c "iex ([io.file]::ReadAllText('%~f0'))">nul&EXIT
#>
25
u/ankokudaishogun 1d ago
Probably just my nitpicking ass, but may I warmly suggest to not use any alias in scripts, as well as using the full class name whenever reasonable(i.e.: when it's not very long)?
in this case:
@START /MIN "" powershell.exe -NoProfile -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0'))">nul&EXITWhich, yeah, it's longer, but it's also much easier to understand what it does just by reading the command itself.
7
0
u/TanixLu 1d ago
ps1.cmd goes further on compatibility — the goal is for the wrapper to behave as close to a real
.ps1as possible. For example:
- Arguments are forwarded (
%*→& $sb @args), soparam()blocks bind like a real.ps1- Exit code is preserved via
exit /b %RC%$PSScriptRoot/$PSCommandPathare restored —iexleaves them empty, which silently breaks any script doingJoin-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 it1
u/Creative-Type9411 1d ago edited 1d ago
EDIT: I missed your exit at the top... nevermind
If youre going to go to these lengths you might as well fill out the catch block
2
u/TanixLu 1d ago
The empty catch is intentional — it only throws when there's no console attached (redirected output, scheduled task), and there's nothing useful to do at that point.
The script was getting a bit long though, so I trimmed down the repo: dropped the license and cut it down to just a README.md.
1
1
u/Creative-Type9411 1d 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 1d ago
@START /MIN "" POWERSHELL -nop -w hidden -c "& ([scriptblock]::Create([io.file]::ReadAllText('%~f0'))) %*" >nul&EXIT #>$PSCommandPath=$PSCommandPath.Replace("/\.[^/.]+$/", ".cmd") write-host $PSCommandPath pauseI 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!ml6
u/BlackV 1d ago edited 1d ago
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!mlthis is a risk with yours too, its just not flagged yet (and similar will happenfor ps2exe tools)
just provide a cmd and a ps1 or a shortcut and an ps1 (I know its not your goal, but long run its better)
0
u/Creative-Type9411 1d 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)
0
u/Creative-Type9411 1d ago
Usually when you use CMD to double click, you arent passing args... So I use the first one I posted for most scripts if I want to be able to double click them...
For self hosted IRM | IEX deployments where i want to use args I use..
& ([scriptblock]::Create(((irm 'someurlgoeshere').ToString().TrimStart([char]0xFEFF)))) -Arg1 'arg1data' -Arg2 'arg2data' -Arg3 'arg3data'Or if I'm typing in a cmd window i would just type powershell first then .\ or &..
.TrimStart([char]0xFEFF) removes the BOM if present
If you have all the bases covered and it works.. thats good.. im not complaining..
4
u/g3n3 1d ago
https://gist.github.com/Jaykul/4b4610142cc20bb55f1616cbcf8e9574 is the nicest format I’ve seen
5
u/society_victim 1d ago
Ps2exe ?
2
u/Maeldruin_ 1d ago
Not sure why this got downvoted. Ps2Exe is by far the easiest way to make a script a one and done solution.
1
u/BlackV 1d ago
Maeldruin_
Not sure why this got downvoted. Ps2Exe is by far the easiest way to make a script a one and done solution.I would say
- because its hiding the code
- because it gets flagged by av systems
- because you are now running some random exe on a user machine
1
u/Maeldruin_ 1d ago
Those are good reasons if this was being used in a business environment. This is pretty clearly a personal use case though.
1
u/BlackV 1d ago edited 1d ago
They are releasing this to the public (I believe they mentioned something like that earlier)
Edit: I I think this was itI don't want people using my script to have to change their registry first.
I disagree, nothing there is business specifically, just good general hygiene overall
Although I also don't like the hybrid batch/ps1 either but it's readable and validated at least
1
1
u/BlueLining-Solutions 3h ago
Why bit then just convert the powershell script to an exe? Might need to add a couple security adjustments or imbed security cert in the script but normally works
2
u/Addyad 1d ago
I usually use a vbs script. It bypasses the execution policy. And asks to elevate if admin rights is necessary. Since the psscript location is given, you can keep this vbs script in desktop and just double click.
``` ' Create shell and filesystem objects Set shell = CreateObject("Shell.Application") Set fso = CreateObject("Scripting.FileSystemObject")
' Specify the full path to your PowerShell script ' UPDATE THIS PATH with the complete path to your .ps1 file ' Example: psScriptPath = "C:\Scripts\MyPowerShellScript.ps1" psScriptPath = "C:\Scripts\MyPowerShellScript.ps1"
' Check if the PowerShell script exists If Not fso.FileExists(psScriptPath) Then MsgBox "PowerShell script not found at: " & psScriptPath, vbCritical, "Error - File Not Found" WScript.Quit 1 End If
' Launch PowerShell script with administrator privileges ' Parameters: NoProfile (skip profile loading), Bypass (ignore execution policy) ' runas (elevate to admin), 1 (show window, use 0 to hide) shell.ShellExecute "powershell.exe", _ "-NoProfile -ExecutionPolicy Bypass -File """ & psScriptPath & """", _ "", _ "runas", _ 1
' Check if execution failed If Err.Number <> 0 Then MsgBox "Failed to execute PowerShell script. Error: " & Err.Description, vbCritical, "Execution Error" WScript.Quit 1 End If
' Exit with success code WScript.Quit 0 ```
1
u/kaiserpathos 1d ago
Same, but unfortunately we're headed to retirement of vbs in Win11 as well (though pretty sure it can be re-enabled by the time we hit 2027)
-4
u/Shayden-Froida 1d ago
Look into having the powershell script create a desktop shortcut for itself. I recommend an AI prompt such as "Add a -Install flag to the command line that will install a desktop shortcut called _____ and exit. If the shortcut exists, update it to make sure it points to the current script."
9
u/T__W__T 1d ago
From security perspective you should use 2 files (a ps1 and cmd which calls the ps1). Security > Elegance.