While you work with a PowerShell session, you can modify its state (or environment) in any number of ways: You might import modules, create functions, specify aliases, or define variables, to name a few. But all of those are transitory: their effects disappear as soon as you close the PowerShell session. However, PowerShell provides a mechanism, called the PowerShell profile, which allows you to recreate any such environmental constructs and settings each time you start up a new PowerShell session. And if one profile is good, wouldn’t more be better? It turns out that any single PowerShell session may use any one (or all) of four different profiles, and different PowerShell hosts provide yet more profile choices: But it is actually much simpler to use than all this might appear, once you appreciate where all the moving parts fit. Let’s take a look.
The Shell in PowerShell
There is a distinction between the shell and the host in PowerShell. Don Jones, in his aptly titled post The Shell vs. The Host, explains that you-as a user typing at the keyboard-do not directly interact with PowerShell’s shell (or engine). Rather more accurately, you interact with a host application (like powershell.exe or powershell_ise.exe) that creates a runspace (an instance of the PowerShell engine). You can actually see the distinction by displaying the contents of the Name property of the $Host
system variable alongside the $ShellId
variable. You will see that, if you examine the values for the three most common PowerShell hosts, they all use the same engine (shell) while each presents a different user interface (host).
Host | $Host.Name | $ShellId |
PowerShell | ConsoleHost | Microsoft.PowerShell |
PowerShell ISE | Windows PowerShell ISE Host | Microsoft.PowerShell |
Visual Studio Package Manager Console | Package Manager Host | Microsoft.PowerShell |
Other PowerShell hosts could potentially have different $ShellId
values (for example, some of the freely available PowerShell IDEs include PowerGUI, PowerShell Analyzer, and PowerShell Plus, but I’ve not checked their $ShellId
values).
The PowerShell Profile
A PowerShell profile is nothing more than a fancy name for a script that runs when a PowerShell host starts. Quoting standard PowerShell help on about_profiles, “You can use the profile as a logon script to customize the environment. You can add commands, aliases, functions, variables, snap-ins, modules, and Windows PowerShell drives. You can also add other session-specific elements to your profile so they are available in every session without having to import or re-create them.”
Each PowerShell host actually supports two profiles, one is user-level, distinct for each user, and the other is system-level, common to all users. This should be a familiar paradigm that you will have seen with many Windows applications. PowerShell adds its own unique twist, though: it similarly distinguishes between host-level (one profile for each host) and system-level (one common profile for all hosts).
Thus, taking all the combinations of users and hosts, you could potentially use any (or all) of four different profiles:
- AllUsersAllHosts
- AllUsersCurrentHost
- CurrentUserAllHosts
- CurrentUserCurrentHost
These profiles all peaceably co-exist so you then need to be aware of the precedence-they are listed above in the order of execution. If you were to define the same variable in all four profiles, the variable will, once you launch a PowerShell host and finally receive a prompt, have the value assigned by the last profile, CurrentUserCurrentHost
, because each successively processed profile will overwrite that variable with its value. Another example, showing cooperation between profiles rather than contention, might be to increment a variable. First define and initialize it to a starting value (e.g. $someVar
= 0) in AllUsersAllHosts
, then increment it in each of the other profiles (e.g. $someVar++
or perhaps $someVar += 5
depending on what you want to do with it).
As to which of the four to use, that largely depends on your own needs: if you use a dedicated computer (i.e. not shared with anyone else) you don’t need to worry about the “all users” profiles. If you do use multiple hosts, you might want to differentiate some things between an “all hosts” profile and a specific host profile. I’ll be giving more details about this below in “How Many Profiles Do You Need?”
The $Profile Variable
To create or edit any of the profiles, of course, you need to know where to find them. PowerShell itself can easily tell you, but it can also just open one for editing without you having to explicitly bother with the path. To see the path, display the value of the $Profile
variable. It reveals a single file path, which is the path to the CurrentUserCurrentHost
profile. In a standard PowerShell host, mine shows this:
1 2 |
PS> $Profile C:\Users\msorens\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 |
Note that this makes no claim about whether the file exists, only that that is the path it must have if it exists. To check existence, use Test-Path
:
1 2 |
PS> Test-Path $Profile True |
If the profile does not exist, you can easily create it:
1 |
PS> New-Item -Type file -Path $Profile -Force |
And if you want to have that in a script, you can then combine the above, creating the file only if necessary:
1 2 |
PS> if (!(Test-Path $Profile)) { New-Item -Type file -Path $Profile -Force } |
Finally, to edit the profile, just invoke your favorite editor on the file, or you can always use the ubiquitous notepad editor:
1 |
PS> notepad $Profile |
All of the above examples are for the “default” (CurrentUserCurrentHost
) profile, as mentioned. But you can apply all of the same commands to any of the four profiles by specific reference; either of these produce the same result:
1 2 |
PS> notepad $Profile PS> notepad $Profile.CurrentUserCurrentHost |
Substitute any of the other three profile property names to access one of the non-default profiles.
Note that it can be tricky to create or save files in system directories (where the “all users” profiles are stored).
You can do so with certain Microsoft tools (e.g. notepad) but you cannot with certain non-Microsoft tools (e.g. my favorite editor, vim). Curiously, vim pretended to work for me but it actually did not: I could create a file, close the editor completely, then re-open the editor and recall the file-yet the file did not show up in Windows Explorer nor was it seen by PowerShell upon startup! (I do not quite know the root cause of this issue, but it is not due to lack of elevated privileges.)
On the other hand, notepad apparently knows the secret incantation, as that works as expected. Another workaround is to create your “all users” profile in your own user directory then just copy or move it into the appropriate system directory.
Profile File Names & Locations
The previous section explained how to edit any of your profiles without actually knowing where they are on the file system, but you are a developer; you have an innate compulsion to know where they are hiding! The table shows the path of each. ( HostId is a placeholder, explained in just a bit.)
Profile | Location |
AllUsersAllHosts | $PsHome\profile.ps1 |
AllUsersCurrentHost | $PsHome\HostId_profile.ps1 |
CurrentUserAllHosts | $Home\Documents\WindowsPowerShell\profile.ps1 |
CurrentUserCurrentHost | $Home\Documents\WindowsPowerShell\HostId_profile.ps1 |
An error that I have seen in a number of articles available on the web is that the “all users” profiles are under $env:WinDir\System32
. That is incorrect ! $PsHome
may coincidentally resolve to $env:WinDir\System32
for some hosts but not for all. As an example, on my system the Visual Studio Package Manager Console stores its “all users” profiles under $env:WinDir\SysWOW64
. (This error appears even in articles from very reputable sources, like this MSDN article.)
Reviewing the locations, it is simple to understand the naming conventions. The system-level profiles-those for all users-are in the system directory pointed to by $PsHome
. The user-level profiles are under each user’s home directory. The one point that needs explanation is the HostId
shown for the host-specific profiles in the table. Unfortunately, this host id bears no direct resemblance to the host description nor to the $Host.Name
property! The way to discover the HostId
is simply to display the value of the $Profile
variable, as it is part of the path. For convenience, here are the HostId
values for the most common hosts:
Host | HostId | $Host.Name |
PowerShell | Microsoft.PowerShell | ConsoleHost |
PowerShell ISE | Microsoft.PowerShellISE | Windows PowerShell ISE Host |
Visual Studio Package Manager Console | NuGet | Package Manager Host |
Another error out there in the wild, though less common, is that HostId
is equivalent to the $ShellId
variable mentioned earlier. That is incorrect! As you have seen, all three of the common hosts shown just above have the same $ShellId
, and that coincidentally matches the HostId
only for the standard PowerShell host. (This error appears in, for example, the book Windows PowerShell Unleashed.)
How Many Profiles Do You Need?
Any standard Windows system has two profiles for the standard PowerShell host, two for the PowerShell ISE host, and two for all hosts-six in all as a minimum. Add in the VS package manager that I have shown, that’s two more. Add other PowerShell IDEs-two more for each. How do you manage so many profiles?
Interpreting official MSDN doctrine ( about_profiles) consider a tripartite solution.
- First, put truly common things in
AllUsersAllHost
. - Second, if there are some peculiarities in particular hosts, use
AllUsersCurrentHost
for those miscreant hosts. - Finally, let each user manage his/her own preferences and settings in user-specific profiles.
But again, there could be many different profiles for “current host”, both at the system level and at the user level. One good reference, as you are mulling over your choices here, is Ed Wilson’s (The Scripting Guy) post Deciding Between One or Multiple PowerShell Profiles. He includes in there a list of advantages and disadvantages for choosing between one profile and multiple profiles. But to me, the advantages of a single profile far outweigh the advantages of multiple profiles. It takes a little more work to set up, but you can still account for host-specific differences while having everything in one place, making it much simpler to maintain over time.
Most of your profile stuff will probably be common to any host, so using a single profile means that any changes down the road will be done in exactly one file. For the stuff that differs amongst hosts, just include a section specific to each host you care about. Mine includes something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
if ($Host.Name -eq 'ConsoleHost') { Import-Module PSReadline # differentiate verbose from warnings! $privData = (Get-Host).PrivateData $privData.VerboseForegroundColor = "cyan" } elseif ($Host.Name -like '*ISE Host') { Start-Steroids Import-Module PsIseProjectExplorer } if (!$env:github_shell) { # not sure why, but this fails in a git-flavored host Add-PSSnapin Microsoft.TeamFoundation.PowerShell } |
Notice how I identify the current host with $Host.Name
then selectively execute code. You see examples above for the standard PowerShell host as well as the PowerShell ISE host. For the former, it loads the PSReadline extension which only works in the PowerShell host. For the latter, it loads the PsISEProjectExplorer and the ISE Steroids module, both of which can only function in the ISE environment.
Finally, if you use Git and the particular installer you used created a Git PowerShell host for you, note that it cannot be distinguished from the standard PowerShell host by the $Host.Name
. Instead, I identified a uniquely defined variable, $env:github_shell
, which is only present in the Git-flavored host.
What to Put in your Profile
The answer to this question depends on your individual needs so it has to be very abstract: put in your profile what you can use to be more productive. There is therefore no single answer to this, but I can certainly provide some suggestions to get your creative juices flowing. You have just seen above a few examples of importing third-party modules to add functionality to the given host. Besides module imports, the following sections provide just a few ideas to get you thinking about what you might want to put in there. Also, take a look at What’s in your PowerShell `profile.ps1` file? (on StackOverflow) for many more examples.
Aliases
Many built-in PowerShell cmdlets have aliases; some have multiple aliases. You can, for example use ls
or dir
or gci
instead of typing Get-ChildItem
. For those that you use regularly that do not provide aliases (or for your own custom functions and cmdlets) you can create your own aliases with Set-Alias
. Set-Alias
is appropriate if you just want to abbreviate the name of the cmdlet or function. But sometimes you might want an alias to include, for example, a cmdlet name plus a parameter that you tend to always use. For these, you can emulate an alias with a simple function. To illustrate, consider the cmdlet Import-Module
. I use it frequently and I prefer all my frequent use cmdlets to just use the first letter of each component. This sets up the alias im
to do that:
1 |
Set-Alias im Import-Module |
But being a developer, I also need to frequently use Import-Module
with the -Force
switch. So for that, I need to resort to a function. For my naming convention, I add the first letter of the switch, hence imf
here:
1 |
function imf($name) { Import-Module $name -force } |
I can then use, e.g. im foobar
to do a vanilla import, or imf foobar
to import with -Force
applied.
Simple Functions
Functions were just mentioned as a means to creating pseudo-aliases, i.e. essentially to save you typing. But, of course, they are not limited to that purpose. You might want to include in your profile a variety of “one-liners” that both save typing and save having to remember details of cmdlets you use less often. Quick, how do you show the last 50 items in your command history? Not certain? The cmdlet to use is Get-History
(it has a standard alias of just the letter h). Is it easy to remember Get-History -Count 50
or just h50? Here’s my definition for h50 (and an h10 thrown in just for good measure):
1 2 |
function h50 { Get-History -Count 50 } function h10 { Get-History -Count 10 } |
Here’s a more interesting function. How would you reveal the cmdlet behind an alias, the path to an executable given just the program name, the parameter sets available for a given cmdlet, or the content of a user-defined function? I use this one-liner to do all that (named after the unix/linux command that performs similarly):
1 |
function which($cmd) { (Get-Command $cmd).Definition } |
Here are some results of using it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
PS> which h Get-History PS> which notepad C:\Windows\system32\notepad.exe PS> which which param($cmd) (Get-Command $cmd).Definition PS> which import-module Import-Module [-Name] <string[]> [-Global] [-Prefix <string>] [-Function <string[ mObject] [-MinimumVersion <version>] [-RequiredVersion <version>] [-ArgumentList Import-Module [-Name] <string[]> -PSSession <PSSession> [-Global] [-Prefix <strin ] [-PassThru] [-AsCustomObject] [-MinimumVersion <version>] [-RequiredVersion <ve arameters>] Import-Module [-Name] <string[]> -CimSession <CimSession> [-Global] [-Prefix <str ce] [-PassThru] [-AsCustomObject] [-MinimumVersion <version>] [-RequiredVersion < sourceUri <uri>] [-CimNamespace <string>] [<CommonParameters>] |
Finally, another handy one-liner that again works like the unix/linux command, reporting your domain-qualified user name:
1 |
function whoami { (get-content env:\userdomain) + "\" + (get-content env:\username } |
Complex Functions
It is likely that you will create some utility functions in the course of your work that are much more involved than a simple one-liner. You could, of course, just embed them in your profile but that tends to make your profile long and messy. If you have a collection of such functions you could always organize them into a true PowerShell module and then just add an Import-Module
in your profile to bring them in. For something in-between those two extremes, consider this approach. In my profile I have this command sequence that brings all the scripts in my local profile_scripts directory into scope at startup:
1 2 3 |
Resolve-Path $PSScriptRoot\profile_scripts\*.ps1 | Where-Object { -not ($_.ProviderPath.Contains(".Tests.")) } | Foreach-Object { . $_.ProviderPath } |
$PSScriptRoot
is a standard system variable that only exists within the context of a running script. It resolves to $Home
\Documents\WindowsPowerShell\. Thus my profile_scripts directory is under that path. Any script (sans any test scripts) is dot-sourced, making it visible in your current scope as soon as you have a prompt after startup.
Perform Actions
The previous items in this section are all passive; they define things for you to use at some future time after startup. But you can also include active items that execute during startup. I highly recommend you use the “don’t let me shoot myself in the foot” cmdlet:
1 |
Set-PSDebug -Strict |
Languages that are weakly-typed (e.g. JavaScript, PowerShell) give you the option of working safely or not, whereas strongly-typed languages generally force you to work safely. Primarily, safely means not allowing you to use a variable before it is declared. That prevents inadvertent mistyping from causing you untold grief trying to figure out why your code is not working. (Presumably, the weakly-typed language folks think some people might find operating safely burdensome, so they make it an option.) Just put the Set-PSDebug
in your profile. Be safe. Please.
Other types of actions you might put in your profile are things like displaying some statistics, e.g. up time, disk space, or PowerShell execution policy. If you administer multiple machines you might want to see details about the machine you have ‘remoted’ into, to make sure you’re on the box you think you are (domain name, computer name, IP address, etc.).
Securing Your Profile
When dealing with computers, security must always be a consideration. You use a firewall and an antivirus to try to keep your system safe. Similarly you need to consider PowerShell scripts that you run- including your own profiles. PowerShell has good support for security, starting with its default setting of not letting you run any scripts; out of the box you can only use PowerShell commands interactively. You need to open your system just enough to enable you to accomplish whatever work you need to do by setting your computer’s execution policy (see Set-ExecutionPolicy).
But as soon as you allow running scripts, there is a chance that you might inadvertently be running a compromised script. This is nothing unique to PowerShell scripts per se- anything on your computer could be compromised-it is just that PowerShell helps you mitigate the situation. And it does this by allowing you to set the execution policy to different security levels per your own needs. You can require that every script must be authenticated, or that just any scripts that you download must be authenticated, among other options. Authentication, in this case, refers to signing scripts with a digital signature (see Set-AuthenticodeSignature) so that, if a file is modified (maliciously or not), the digital signature would detect the tampering and prevent the script from running.
Managing security for your PowerShell scripts (including your profiles), however, is not a trivial endeavor. (It would more than double the length of this article!) But a lot of good information is already out there to guide you. I would recommend starting with another article here on Simple-Talk, Nicolas Prigent’s PowerShell Day-to-Day SysAdmin Tasks: Securing Scripts. There are also several good references in PowerShell’s own documentation: about_signing gives a good introduction to the topic; New-SelfSignedCertificate lets you create your own self-signed certificates, and Get-ChildItem for Certificate reveals the little-known differences in Get-ChildItem
when referencing your certificate store. Microsoft provides an old but still useful reference on Code-Signing Best Practices. And Geoff Bard’s Signing PowerShell Scripts is worth a look as well.
Get That Profile Out of the Way!
Now you know how to set up your profile, why it is useful, what to do with it, and how to safeguard it. But like any super power, you have to be cognizant of its dark side. Well, not so much a dark side per se, but that there are times you simply do not want your profile(s) to get in your way. Or, more poignantly, other people’s profiles.
There are a variety of situations where you might want to actually execute powershell.exe either with a command literal or a script file to run. Here is just one example: say you have created a PowerShell script that you want to share with a colleague. Unlike batch files, you cannot just double-click a PowerShell script to execute it; that’s part of PowerShell’s security modus operandi to keep your system safe. But that is easy to circumvent (not that I am recommending it!) by creating a standard Windows shortcut targeting powershell.exe with your PowerShell script file as a parameter.
Another, perhaps more legitimate use, would be to run a PowerShell script or command within a build file. Since MSBuild does not innately know how to run PowerShell scripts, you would typically execute a script by supplying it as an argument to powershell.exe.
Anytime you run powershell.exe, though, you are opening a new PowerShell host. And what happens when you open a host? It runs any (or all) of your four profiles! But almost any time you are opening a host by directly invoking powershell.exe you do not want your profiles to run, neither for the overhead nor for the possible conflicts that might ensue. Keep in mind, if someone else is running a build where you have introduced a command to run powershell.exe, it is their profile that will be run on their machine, and you have no notion of what might be lurking there. Further still, you do not want to depend on something in a profile because the first time someone runs your build who does not know of the dependency, it will (possibly mysteriously) fail. So it is safest all around to simply adopt the best practice of always ignoring profiles when you invoke powershell.exe. (I don’t mean you should ignore them, rather that you should tell PowerShell to ignore them, of course!
So after all that suspenseful buildup, the denouement may be a bit anticlimactic: simply add a -NoProfile
as a parameter to powershell.exe.
Conclusion
The PowerShell profile is your friend. With the roadmap set out in this article, you have seen the types of profiles available to you and can select the ones that will work for you. Or you can choose to use a single profile, distinguishing any host-specific items as needed. The profile is a simple yet powerful tool available to you, not at all complicated to use, and its uses are limited only by your imagination. About the only downside is all the time you are now going to spend searching for handy and clever things to add to your profile. (Did I mention you should look at including the Go-Shell module…?)
Load comments