Quick Links

Windows and PowerShell have built-in security features and default configurations intended to prevent end-users from accidentally launching scripts in the course of their daily activities. However, if your daily activities routinely involve writing and running your own PowerShell scripts, this can be more of a nuisance than a benefit. Here, we'll show you how to work around these features without completely compromising on security.

How and why Windows & PowerShell prevent script execution.

PowerShell is effectively the command shell and scripting language that's intended to replace CMD and batch scripts on Windows systems. As such, a PowerShell script can pretty much be configured to do anything you could do manually from the command line. That equates to making practically any change possible on your system, up to the restrictions in place on your user account. So, if you could just double-click a PowerShell script and run it with full Administrator privileges, a simple one-liner like this could really wreck your day:

Get-ChildItem "$env:SystemDrive\" -Recurse -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue

DO NOT run the above command!

That simply goes through the file system and deletes whatever it can. Interestingly, this may not render the system inoperable as quickly as you might think - even when run from an elevated session. But if someone calls you after running this script, because they suddenly can't find their files or run some programs, "turning it off and on again" will probably just lead them into Windows Startup Repair where they're going to be told there's nothing that can be done to fix the problem. What could be worse is, instead of getting a script that just trashes their file system, your friend might be tricked into running one that downloads and installs a keylogger or remote access service. Then, instead of asking you questions about Startup Repair, they may end up asking the police some questions about bank fraud!

By now it should be obvious why certain things are needed to protect end-users from themselves, so to speak. But power users, system administrators, and other geeks are generally (though there are exceptions) a bit more wary of these threats, knowing how to spot and easily avoid them, and just want to get on with getting their work done. To do this, they'll have to either disable or work around a few road blocks:

  • PowerShell does not allow external script execution by default.The ExecutionPolicy setting in PowerShell prevents execution of external scripts by default in all versions of Windows. In some Windows versions, the default doesn't allow script execution at all. We showed you how to change this setting in How to Allow the Execution of PowerShell Scripts on Windows 7, but we'll cover it on a few levels here as well.
  • PowerShell is not associated to the .PS1 file extension by default.We brought this up initially in our PowerShell Geek School series. Windows sets the default action for .PS1 files to open them in Notepad, instead of sending them to the PowerShell command interpreter. This is to directly prevent accidental execution of malicious scripts when they're simply double-clicked.
  • Some PowerShell scripts won't work without Administrator permissions.Even running with an Administrator-level account, you still need to get through User Account Control (UAC) to perform certain actions. For command-line tools, this can be a bit cumbersome to say the least. We don't want to disable UAC, but it's still nice when we can make it a bit easier to deal with.

These same issues are brought up in How to Use a Batch File to Make PowerShell Scripts Easier to Run, where we walk you through writing a batch file to temporarily get around them. Now, we're going to show you how to set your system up with a more long-term solution. Bear in mind that you should not generally make these changes on systems that aren't exclusively used by you - otherwise, you're putting other users at higher risk of running into the same problems these features are intended to prevent.

Changing the .PS1 file association.

The first, and perhaps foremost, annoyance to get around is the default association for .PS1 files. Associating these files to anything other than PowerShell.exe makes sense for preventing accidental execution of undesirable scripts. But, considering that PowerShell comes with an Integrated Scripting Environment (ISE) which is specifically designed for editing PowerShell scripts, why would we want to open .PS1 files in Notepad by default? Even if you're not ready to fully switch to enabling double-click-to-run functionality, you'll probably want to tweak these settings.

You could change the .PS1 file association to whatever program you want with the Default Programs control panel, but digging directly into the Registry will give you a bit more control over exactly how the files will be opened. This also lets you set or change additional options which are available in the context menu for .PS1 files. Don't forget to make a backup of the registry before you do this!

The registry settings controlling how PowerShell scripts are opened are stored in the following location:

HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell

To explore these settings before we go about changing them, have a look at that key and its sub-keys with Regedit. The Shell key should just have one value, "(Default)", which is set to "Open". This is a pointer to the default action for double-clicking the file, which we'll see in the sub-keys.

Expand the Shell key, and you'll see three sub-keys. Each of these represents an action you can perform which is specific to PowerShell scripts.

Shell-Key

You can expand each key to explore the values within, but they basically equate to the following defaults:

  • 0 - Run with PowerShell. "Run with PowerShell" is actually the name of an option already in the context menu for PowerShell scripts. The text is just pulled from another location instead of using the key name like the others. And it's still not the default double-click action.
  • Edit - Open in PowerShell ISE. This makes much more sense than Notepad, but you still have to right-click the .PS1 file to do it by default.
  • Open - Open in Notepad. Note that this key name is also the string stored in the "(Default)" value of the Shell key. This means double-clicking the file will "Open" it, and that action is normally set to use Notepad.

If you want to stick with the pre-built command strings already available, you can just change the "(Default)" value in the Shell key to match the name of the key that matches what you want a double-click to do. This can easily be done from within Regedit, or you could use lessons learned from our tutorial on exploring the registry with PowerShell (plus a small PSDrive tweak) to begin building a reusable script that can configure your systems for you. The below commands must be run from an elevated PowerShell session, similar to running CMD as Administrator.

First, you'll want to configure a PSDrive for HKEY_CLASSES_ROOT since this isn't set up by default. The command for this is:

New-PSDrive HKCR Registry HKEY_CLASSES_ROOT

Now you can navigate and edit registry keys and values in HKEY_CLASSES_ROOT just like you would in the regular HKCU and HKLM PSDrives.

To configure double-clicking to launch PowerShell scripts directly:

Set-ItemProperty HKCR:\Microsoft.PowerShellScript.1\Shell '(Default)' 0

To configure double-clicking to open PowerShell scripts in the PowerShell ISE:

Set-ItemProperty HKCR:\Microsoft.PowerShellScript.1\Shell '(Default)' 'Edit'

To restore the default value (sets double-click to open PowerShell scripts in Notepad):

Set-ItemProperty HKCR:\Microsoft.PowerShellScript.1\Shell '(Default)' 'Open'

That's just the basics of changing the default double-click action. We'll go into more detail on customizing how PowerShell scripts are handled when they're opened in PowerShell from Explorer in the next section. Keep in mind that scoping prevents PSDrives from persisting across sessions. So, you'll probably want to include the New-PSDrive line at the start of any configuration script you build for this purpose, or add it to your PowerShell profile. Otherwise, you'll need to run that bit manually before trying to make changes this way.

Changing the PowerShell ExecutionPolicy setting.

PowerShell's ExecutionPolicy is another layer of protection against execution of malicious scripts. There are multiple options for this, and a couple different ways it can be set. From most to least secure, the available options are:

  • Restricted - No scripts are allowed to run. (Default setting for most systems.) This will even prevent your profile script from running.
  • AllSigned - All scripts must be digitally signed by a trusted publisher to run without prompting the user. Scripts signed by publishers explicitly defined as untrusted, or scripts not digitally signed at all, will not run. PowerShell will prompt the user for confirmation if a script is signed by a publisher not yet defined as trusted or untrusted. If you haven't digitally signed your profile script, and established trust in that signature, it won't be able to run. Be careful which publishers you trust, as you can still end up running malicious scripts if you trust the wrong one.
  • RemoteSigned - For scripts downloaded from the Internet, this is effectively the same as "AllSigned". However, scripts created locally or imported from sources other than the Internet are allowed to run without any confirmation prompt. Here, you'll need to also be careful which digital signatures you trust but even be more careful of the non-signed scripts you choose to run. This is the highest security level under which you can have a working profile script without having to digitally sign it.
  • Unrestricted - All scripts are allowed to run, but a confirmation prompt will be required for scripts from the Internet. From this point on, it's entirely up to you to avoid running untrustworthy scripts.
  • Bypass - Everything runs without a warning. Be careful with this one.
  • Undefined - No policy is defined in the current scope. This is used to allow fall-back to policies defined in lower scopes (more details below) or to the OS defaults.

As suggested by the description of Undefined, the above policies can be set in one or more of several scopes. You can use Get-ExecutionPolicy, with the -List parameter, to see all of the scopes and their current configuration.

ExecutionPolicy-List

The scopes are listed in precedence order, with the topmost defined scope overriding all others. If no policies are defined, the system falls back to its default setting (in most cases, this is Restricted).

  • MachinePolicy represents a Group Policy in effect at the Computer level. This is generally applied only in a domain, but can be done locally as well.
  • UserPolicy represents a Group Policy in effect on the user. This is also typically only used in enterprise environments.
  • Process is a scope specific to this instance of PowerShell. Changes to the policy in this scope will not affect other running PowerShell processes, and will be ineffective after this session is terminated. This can be configured by the -ExecutionPolicy parameter when PowerShell is launched, or it can be set with the proper Set-ExecutionPolicy syntax from within the session.
  • CurrentUser is a scope that is configured in the local registry and applies to the user account used to launch PowerShell. This scope can be modified with Set-ExecutionPolicy.
  • LocalMachine is a scope configured in the local registry and applying to all users on the system. This is the default scope that is changed if Set-ExecutionPolicy is run without the -Scope parameter. As it applies to all users on the system, it can only be changed from an elevated session.

Since this article is mainly about getting around security to facilitate usability, we're just concerned about the lower three scopes. The MachinePolicy and UserPolicy settings are really useful only if you want to enforce a restrictive policy that isn't so simply bypassed. By keeping our changes to the Process level or below, we can easily use whatever policy setting we deem appropriate for a given situation at any time.

To retain some balance between security and usability, the policy shown in the screenshot is probably best. Setting the LocalMachine policy to Restricted generally prevents running scripts by anyone other than you. Of course, this can be bypassed by users who know what they're doing without much effort. But it should keep any non-tech-savvy users from accidentally triggering something catastrophic in PowerShell. Having the CurrentUser (i.e.: you) set as Unrestricted allows you to manually execute scripts from the command line however you like, but does retain a reminder of caution for scripts downloaded from the Internet. The RemoteSigned setting at the Process level would need to be done in a shortcut to PowerShell.exe or (as we'll do below) in the Registry values that control the behavior of PowerShell scripts. This will allow easy double-click-to-run functionality for any scripts you write, while putting up a stronger barrier against unintentional execution of (potentially malicious) scripts from external sources. We want to do this here since it's much easier to accidentally double-click a script than it generally is to call it manually from an interactive session.

To set the CurrentUser and LocalMachine policies as in the screenshot above, run the following commands from an elevated PowerShell session:

Set-ExecutionPolicy Restricted

Set-ExecutionPolicy Unrestricted -Scope CurrentUser

To enforce the RemoteSigned policy on scripts run from Explorer, we'll have to change a value inside of one of the registry keys we were looking at earlier. This is particularly important because, depending on your PowerShell or Windows version, the default configuration may be to bypass all ExecutionPolicy settings except AllSigned. To see what the current configuration is for your computer, you can run this command (making sure the HKCR PSDrive is mapped first):

Get-ItemProperty HKCR:\Microsoft.PowerShellScript.1\Shell\Command | Select-Object '(Default)'

Your default configuration will probably be one of the following two strings, or something fairly similar:

(Seen on Windows 7 SP1 x64, with PowerShell 2.0)

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-file" "%1"

(Seen on Windows 8.1 x64, with PowerShell 4.0)

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & '%1'"

The first one isn't too bad, as all it does is execute the script under the existing ExecutionPolicy settings. It could be made better, by enforcing tighter restrictions for a more accident-prone action, but this wasn't originally intended to be triggered on a double-click anyway, and the default policy is usually Restricted after all. The second option, however, is a full bypass of whatever ExecutionPolicy you're likely to have in place - even Restricted. Since the bypass will be applied in the Process scope, it only affects the sessions that are launched when scripts are run from Explorer. However, this means that you could end up launching scripts that you might otherwise expect (and want) your policy to forbid.

To set the Process-level ExecutionPolicy for scripts launched from Explorer, in line with the screenshot above, you'll need to modify the same registry value we just queried. You can do it manually in Regedit, by changing it to this:

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-ExecutionPolicy" "RemoteSigned" "-file" "%1"

Regedit-RemoteSigned

You can also change the setting from within PowerShell if you prefer. Remember to do this from an elevated session, with the HKCR PSDrive mapped.

Set-ItemProperty HKCR:\Microsoft.PowerShellScript.1\Shell\Command '(Default)' '"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-ExecutionPolicy" "RemoteSigned" "-file" "%1"'

Run PowerShell scripts as Administrator.

Just as it is a bad idea to disable UAC entirely, it's also bad security practice to run scripts or programs with elevated privileges unless you actually need them to perform operations which require Administrator access. So, building the UAC prompt into the default action for PowerShell scripts is not recommended. However, we can add a new context menu option to allow us to easily run scripts in elevated sessions when we need to. This is similar to the method used to add "Open with Notepad" to the context menu of all files - but here we're only going to target PowerShell scripts. We're also going to carry over some techniques used in the previous article, where we used a batch file instead of registry hacks to launch our PowerShell script.

To do this in Regedit, go back into the Shell key, at:

HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell

In there, create a new sub-key. Call it "Run with PowerShell (Admin)". Underneath that, create another sub-key called "Command". Then, set the "(Default)" value under Command to this:

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" ""& {Start-Process PowerShell.exe -ArgumentList '-ExecutionPolicy RemoteSigned -File \"%1\"' -Verb RunAs}"

Regedit-RunAsAdmin

Doing the same in PowerShell will actually need three lines this time. One for each new key, and one to set the "(Default)" value for Command. Don't forget elevation and the HKCR mapping.

New-Item 'HKCR:\Microsoft.PowerShellScript.1\Shell\Run with PowerShell (Admin)'

New-Item 'HKCR:\Microsoft.PowerShellScript.1\Shell\Run with PowerShell (Admin)\Command'

Set-ItemProperty 'HKCR:\Microsoft.PowerShellScript.1\Shell\Run with PowerShell (Admin)\Command' '(Default)' '"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" ""& {Start-Process PowerShell.exe -ArgumentList ''-ExecutionPolicy RemoteSigned -File \"%1\"'' -Verb RunAs}"'

Also, pay careful attention to the differences between the string that's being put in through PowerShell and the actual value that's going into the Registry. Particularly, we've got to wrap the whole thing in single-quotes, and double-up on the internal single-quotes, in order to avoid errors in command parsing.

Now you should have a new context-menu entry for PowerShell scripts, called "Run with PowerShell (Admin)".

RightClick

The new option will spawn two consecutive PowerShell instances. The first is just a launcher for the second, which uses Start-Process with the "-Verb RunAs" parameter to request elevation for the new session. From there, your script should be able to run with Administrator privileges after you click through the UAC prompt.

Finishing touches.

There's just a couple more tweaks to this that can help make life a bit easier still. For one, how about getting rid of the Notepad function entirely? Simply copy the "(Default)" value from the Command key under Edit (below), into the same location under Open.

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe" "%1"

Or, you can use this bit of PowerShell (with Admin & HKCR of course):

Set-ItemProperty HKCR:\Microsoft.PowerShellScript.1\Shell\Open\Command '(Default)' '"C:\Windows\System32\WindowsPowerShell\v1.0\powershell_ise.exe" "%1"'

One more minor annoyance is the console's habit of disappearing once a script is complete. When that happens, we don't have any chance to review the script output for errors or other useful information. This can be taken care of by putting a pause at the end of each of your scripts, of course. Alternately, we can modify the "(Default)" values for our Command keys to include the "-NoExit" parameter. Below are the modified values.

(Without Admin access)

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-NoExit" "-ExecutionPolicy" "RemoteSigned" "-file" "%1"

(With Admin access)

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" ""& {Start-Process PowerShell.exe -ArgumentList '-NoExit -ExecutionPolicy RemoteSigned -File \"%1\"' -Verb RunAs}"

And of course, we'll give you those in PowerShell commands too. Last reminder: Elevation & HKCR!

(Non-Admin)

Set-ItemProperty HKCR:\Microsoft.PowerShellScript.1\Shell\Command '(Default)' '"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-NoExit" "-ExecutionPolicy" "RemoteSigned" "-file" "%1"'

(Admin)

Set-ItemProperty 'HKCR:\Microsoft.PowerShellScript.1\Shell\Run with PowerShell (Admin)\Command' '(Default)' '"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" ""& {Start-Process PowerShell.exe -ArgumentList ''-NoExit -ExecutionPolicy RemoteSigned -File \"%1\"'' -Verb RunAs}"'

Taking it for a spin.

To test this out, we're going to use a script that can show us the ExecutionPolicy settings in place and whether or not the script was launched with Administrator permissions. The script will be called "MyScript.ps1" and be stored in "D:\Script Lab" on our sample system. The code is below, for reference.

if(([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))

{Write-Output 'Running as Administrator!'}

else

{Write-Output 'Running Limited!'}

Get-ExecutionPolicy -List

Using the "Run with PowerShell" action:

Limited-ExecutionPolicy

Using the "Run with PowerShell (Admin)" action, after clicking through UAC:

Admin-ExecutionPolicy

To demonstrate the ExecutionPolicy in action at the Process scope, we can make Windows think the file came from the Internet with this bit of PowerShell code:

Add-Content -Path 'D:\Script Lab\MyScript.ps1' -Value "[ZoneTransfer]`nZoneId=3" -Stream 'Zone.Identifier'

RemoteSigned-Error

Fortunately, we had -NoExit enabled. Otherwise, that error would have just blinked on by, and we wouldn't have known!

The Zone.Identifier can be removed with this:

Clear-Content -Path 'D:\Script Lab\MyScript.ps1' -Stream 'Zone.Identifier'


Useful References: