PowerShell with a Purpose Blog

Guest Column: Dude, Where's my Profile?

I'm making Fridays available for articles from guest authors, to help lend a broader perspective and unique knowledge to the tips and tricks you read here. My first guest author is Mike Pfeiffer, who offers some answers to why PowerShell profiles can seem "broken" when it comes to remoting.

So, you've finally got your PowerShell profile setup the way you want it, and you've probably added a number of custom functions, variables, and aliases to it. You've figured out that you can also use a profile to automatically load modules and snap-ins, and having all of this done for you every time you start the shell has proven to be a real time saver. Now you've moved on to mastering other things, like PowerShell Remoting and Background Jobs. As you send commands to multiple computers, work with individual systems though interactive sessions, or execute long running tasks in the background, you realize that the customizations added to your current shell environment aren't available. You sit back and think to yourself...dude, where's my profile?

Let's talk a little about what's going on here. When you enter an interactive remote session, invoke a command on a remote computer, or start a background job, you're really just starting up a new instance of PowerShell. The stuff in your local session, like user defined variables and custom functions aren't available in these instances, and your local profile script isn't loaded. Another thing that can be counter intuitive is that even though you may have setup a profile for yourself on all of your computers, it does not run when you start up a remote session. Nothing is broken, that's just how it works. All of this behavior is, as they say, by design.

Don't worry though; you're not out of luck. Let's say that you've got a function called Get-ServerInfo in your profile that retrieves detailed system information, and you need to be able to run this function in a remote session or a background job. There are a few quick workarounds you can use.

First, we'll focus on getting this done through remoting. Let's assume you've enabled remoting on a couple of machines called server1 and server2, and you'll be connecting from a workstation in the same domain using your logged on credentials. You can create a remote session to these computers, load the local profile script, and then run the function. Here's an example:

$s = New-PSSession -ComputerName server1,server2
Invoke-Command -Session $s -FilePath $profile
Invoke-Command -ScriptBlock {Get-ServerInfo} -Session $s

The first command uses the New-PSSession cmdlet to create a persistent connection to both computers, and the result of that command is stored in a variable called $s. The second command uses Invoke-Command to load the local $profile script into the remote sessions. Notice the -FilePath parameter used here with Invoke-Command; this takes the contents of the specified script, converts it to a script block, and then transfers and runs that code in each remote session. Of course, in addition to profiles, any .ps1 script can be used with the -FilePath parameter. After running the above commands, the local profile is loaded into each remote session, and Invoke-Command is then used to run the Get-ServerInfo function in parallel on each server.

If you want to work interactively on a single server, you can create a session using the same technique, and then use Enter-PSSession to work one-to-one:

$s = New-PSSession -ComputerName server1
Invoke-Command -Session $s -FilePath $profile
Enter-PSSession $s

These commands will take you to a prompt on a remote server named server1 where the local profile script has been loaded into the remote session. At that point, you're ready to start running commands.

Let's consider another example. Instead of using a local profile that contains the Get-ServerInfo function, imagine that it was setup on each remote computer under your Windows user profile. In this case, you can simply dot-source the script, and then run the function:

Invoke-Command -Scriptblock {. $env:userprofile\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 ; Get-ServerInfo} -ComputerName server1,server2

As you can see, there are two commands within the -Scriptblock parameter and they're separated using a semi-colon. The first command dot-sources the profile script located on the remote server. This assumes that you've created a profile on each remote computer using the CurrentUserCurrentHost profile type, which is defined by the $profile automatic variable. Once that script has been dotted, the functions defined within it are made available, and then you can call the Get-ServerInfo function.

Another option is to use Invoke-Command to dot-source the profile within a session:

$s = New-PSSession -ComputerName server1
Invoke-Command -ScriptBlock {. $env:userprofile\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1} -Session $s

At this point, you can use either Enter-PSSession or Invoke-Command to work with the $s session.

And finally, here's a workaround that will get your profile loaded in a background job:

Start-Job {Get-ServerInfo} -InitializationScript {. $env:userprofile\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1}

The scriptblock provided to the -InitializationScript parameter runs before the job starts. This will load the profile and make the Get-ServerInfo function available within the job. Just as before, this example assumes you've created a local profile based on the CurrentUserCurrentHost profile type. The $env:userprofile variable is used again here in the file path when dotting the script. This is because the $profile automatic variable is not available in this instance, and this is true for both remote sessions and background jobs.

Mike Pfeiffer has been in the IT industry for 13 years, and he currently serves as the co-leader of the Arizona PowerShell User Group. You can catch him on twitter as @mike_pfeiffer, or contact him through his blog at mikepfeiffer.net
Hide comments


  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.