Powershell: Starting a Script the Easy Way

Powershell scripts can be very practical tools, but they lack an easy Just Do It mode:
Even as a developer/administrator/power-user, one has to remove several obstacles before you can use it; and if you plan to deploy a script to (normal) users, you should take care of some issues before.

Here are a few tips for the latter case: Making a Powershell script to be started easily by normal users (e.g. non-Powershell users, non-IT staff, family members etc.).

Let’s start with a list of the commonly encountered issues when giving a Powershell script to users; the items are based on a blog post by Daniel Schroeder and my own experience:

  1. Double clicking a *.ps1 file by default will not run the script, but will only just open it in a text editor (it’s a security feature).

  2. Even if you invoke the script directly from a Powershell prompt, by default the execution policy is set to Restricted and will prevent a start; one has to explicitly allow Powershell commands to be executed on the machine; again, a security feature.

  3. The script may need administrative privileges; so you’d have to close your current Powershell session, start a new one with “Run as administrator…” and try again (or the script asks during its runitime for credentials, but that has to be considered by the author).

  4. For normal users rather unlikely to be an issue, but maybe for more advanced ones, which also dabble in Powershell:
    Settings or variables in their personal Powershell profiles may have an unexpected/unwanted influence on your script.

A lot of hassle, even for IT-affine users.

You could change settings and policies once and for all on the machine (given you have the permissions), either by calling the proper commands from an Admin-Powershell prompt, or fiddling directly in the Registry or changing the File association rules – but of course that is also a security concern and not wanted (or even possible) for scripts that are handed over to “simple users”.

So, how can you work around these issues? Let’s tackle them, one by one…

Double clicking a *.ps1 file has not the desired effect

The most common workaround for this is to prepare a second script: A simple batch or command file (*.bat or *.cmd) that the user starts instead of the Powershell file (*.ps1) directly.

This batch/command file will then simply start a Powershell session with the given script:

@echo off
powershell.exe -File "...path\to\the\script.ps1"

In the following sections, we will add more parameters to this file to solve the other problems.

And in case you didn’t know: The echo off in a batch file turns off the default command echoing.
And @echo off silently turns off this command itself, so that only the intended output is actually written to the console; I’ll omit it for brevity reasons in the future examples.

Execution Policy prevents running

But that is not enough. We also need to modify (temporarily, for this session/process) some settings on behalf of the user, so that the Powershell script will work.

In that batch/command file, we thus add -ExecutionPolicy Bypass:

powershell.exe -ExecutionPolicy Bypass -File "...path\to\the\script.ps1"
The order of the parameters is important!
Otherwise thing will not work (as expected); at least that was the case on my tests under Windows 10 (v1903):
-ExecutionPolicy Bypass must come before the script name/command block!

Administrative Privileges are needed

For that, we will add another layer of indirection:

The user starts the batch/command file; that invokes a Powershell session, which in turn starts another Powershell process that will prompt for administrator credentials (username, password) before it continues to start the actual Powershell script file.

This is achieved by adding a script block (-Command) that uses the Start-Process cmdlet with the -Verb RunAs argument (amongst others).
Take note of the right amount and matching of single (') and double quote (") signs!

(This is one line; split here for readibilty purposes.)

powershell.exe -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell
  -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""...path\to\the\script.ps1""' -Verb RunAs}";

Of course, this is only useful for users that can provide credentials for such an account with administrative privileges on the machine – it is a hard security feature: The above solution will not magically give a normal user admin rights!

And here is how to pass in named parameters (this is one line; split here for readibilty purposes):

powershell.exe -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell
  -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File "...path\to\the\script.ps1"
  -Param1Name """"Param 1 Value"""" -Param2Name """"Param 2 value""""' -Verb RunAs}";

The many, many double-quotes are not a mistake; to quote Daniel Schroeder:

And yes, the PowerShell script name and parameters need to be wrapped in 4 double quotes in orde to properly handle paths/values with spaces.

User’s Powershell profile may intervene with your script

Add the -NoProfile switch to your call to run the Powershell:

powershell.exe -ExecutionPolicy Bypass -NoProfile ...

Because you may not know what happens in some user/machine profiles.
(And the start time may also be a bit quicker without the need to load a profile first.)

Keep the Powershell console window open after the script finishes

Call PowerShell.exe with the -NoExit switch (Do not exit after running the startup commands.), e.g.:

powershell.exe -NoExit -File "...path\to\the\script.ps1"

Or else, add a block like this to the end of your script; again a quote from Daniel Schroeder:

This will prompt the user for keyboard input before closing the PowerShell console window.
This is useful because it allows users to read any errors that your PowerShell script may have thrown before the window closes, or even just so they can see the “Everything completed successfully” message that your script spits out so they know that it ran correctly.

if ($Host.Name -eq "ConsoleHost")
    Write-Host "Press any key to continue..."
    $Host.UI.RawUI.FlushInputBuffer()   # Make sure buffered input doesn't "press a key" and skip the ReadKey().
    $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyUp") > $null
    # ... or try the simple version:
    # Read-Host -Prompt "Press any key to exit."

For GUI-only scripts: Hide the console window

Some Powershell scripts use a GUI instead of the text console, so displaying an unneeded Powershell prompt in the background while the user interacts only via windows with the script seems inelegant.

To prevent that, you can use these pretty much self-explanatory arguments:

powershell.exe -ExecutionPolicy Bypass -NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -File "...path\to\the\script.ps1"

But, some remarks:

Start script from a scheduled task or GPO and/or provide arguments

Here are some tips on how to let another service run a Powershell script:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\path\to\the\script.ps1

powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\path\to\the\script.ps1 -NoExit

powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& C:\path\to\the\script.ps1 -MyParam MyValue"

cmd.exe /c powershell.exe -NoProfile -ExecutionPolicy Bypass -Force -WindowStyle Hidden
        (cont'd)          -NonInteractive -NoLogo -File "C:\Some\Path\script.ps1"