Starting PowerShell Automatically – Revisited

A few months ago, I posted a quick blog on how I set PowerShell to start automatically when I log in. Well, it occurred to me that I should extend that to show you how I set the console window parameters so that my PowerShell windows are the right size and font, since on all my systems I use the same basic settings (except for my Surface Book with the 4K addon monitor — my old eyes need a bit bigger on that, so I adjusted the values accordingly.)

The function I use to set the console size, font and font weight is: (FontSize here translates to 18 point on my FullHD systems)

Function Set-myDefaultFont () {
   $64bit = "HKCU:\Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe"
   $Default = "HKCU:\Console"
   Set-ItemProperty -Path $Default -Name FaceName   -Value "Consolas"
   Set-ItemProperty -Path $Default -Name FontSize   -Type DWord -Value 0x00120000
   Set-ItemProperty -Path $Default -Name FontWeight -Type DWord -Value 0x000002bc
   Set-ItemProperty -Path $Default -Name FontFamily -Type DWord -Value 0x00000036 
   Set-ItemProperty -Path $Default -Name QuickEdit  -Type DWord -Value 0x00000001
   Set-ItemProperty -Path $64bit -Name FaceName     -Value "Consolas"
   Set-ItemProperty -Path $64bit -Name FontSize     -Type DWord -Value 0x00120000
   Set-ItemProperty -Path $64bit -Name FontWeight   -Type DWord -Value 0x000002bc
   Set-ItemProperty -Path $64bit -Name FontFamily   -Type DWord -Value 0x00000036 
   Set-ItemProperty -Path $64bit -Name QuickEdit    -Type DWord -Value 0x00000001

That’s all there is to it. I’ve added this function to the Set-myPowerShellStart.ps1 script, and I call that whenever I create or log onto a new system. If you prefer different sizes or a different font, simply change the values to match what you need. For example, if you want a 16 point font, try a value of 0x00100000.

ETA: Fixed value of FontSize.

Testing for Location on a Laptop

On my laptops, I have a different set of drive maps when I’m at home, or on the road. At home, I map to various local domain resources, but when I’m on the road, those resources aren’t available and I need to create “local” maps to versions that I can sync up to those domain resources when I get back home. But how to know where I am? Use PowerShell’s Test-Connection, of course, the PowerShell version of “ping”.


The problem is further complicated by the dual nature of my home network – I have two different subnets, and the resources could be reachable on either, depending on the wireless network to which I’m connected.


So, how to handle? I’ve chosen to use the logical OR operator for this test. I could have used an if {} elseif {} construction, but that seemed clunky, so I went with a simple test:

$AtHome= ((test-connection `
                           -Count 1 `
                           -TimeToLive 1 `
                           -quiet) `
         -OR (Test-Connection `
                           -count 1 `
                           -TimeToLive 1 `

The test tells me if my domain controller is reachable on either of the subnets used. Because PowerShell uses shortcut processing for logical operators, if the test succeeds at, it immediately returns $True to the $AtHome variable and doesn’t bother processing the second test. But if it fails the first test, it then tries the second subnet. If it can reach, it sets $AtHome to $True. If not, it sets $AtHome to $False.


Now, when I call my drive mapping script, and I pass it the result of $AtHome. That script knows to modify the behaviour based on the Boolean value of $AtHome…

Map-myDrive -SMB -AtHome $AtHome -Force

Mapping Drives Revisited

In my old drive mapping post, I was forced to do some fairly ugly stuff because I had to call the old net use command. Yech. Eventually, we got New-PSDrive, and that helped, but in PowerShell v5 (Windows 10’s version), we get New-SmbMapping and it actually works. (New-SmbMapping was added earlier, but there were issues. Those appear to be resolved in the final version of v5.)


When New-PSDrive finally had persistent drive mappings, I replaced my my old MapDrives.cmd file with a new MapDrives.ps1 that used the New-PSDrive syntax:

New-PSDrive -Name I -root \\srv2\Install -Scope Global -PSProv FileSystem -Persist

A bit awkward, but it works. However, it sometimes ran into problems with drives that were mapped with net use, so I was glad when we finally got a useful version of New-SmbMapping. Now the syntax for mapping my I: drive to \\srv1\install is:

New-SmbMapping -LocalPath I: -RemotePath \\srv2\Install -Persistent $True

Great. But it doesn’t have a -Force parameter, so I can’t tell it to override any maps that already exist. That requires cleaning up the old maps before I make new ones. For that, we have Remove-SmbMapping and Remove-PsDrive.

function Remove-myMaps () {
   $DriveList = (Get-SmbMapping).LocalPath
   write-verbose "Removing drives in DriveList: $drivelist"
   Foreach ($drive in $DriveList) {
      $psDrive = $drive -replace ":" #remove unwanted colon from PSDrive name
      Write-Verbose "Running Remove-SmbMapping -LocalPath $Drive -Force -UpdateProfile"
      Remove-SmbMapping -LocalPath $Drive -Force -UpdateProfile 
      If ( (Get-PSDrive -Name $psDrive) 2>$Null ) {
      Write-Verbose "Running Remove-PsDrive -Name $psDrive -Force "
      Remove-PSDrive -Name $psDrive -Force
      write-host " "
      $DriveList = (Get-SMBMapping).LocalPath
      Write-Verbose "The drive list is now: $DriveList"

As you might have noticed, PsDrives don’t have a colon, and SmbMapping drives do. But PowerShell gives us the useful -replace operator, allowing us to simply remove the stray colon from the drive letter that SmbMapping has.

So, here’s the entire script. Feel free to use it as the basis for your own mappings, but please, respect the copyright and don’t republish it but link to here instead. Thanks. :)


Maps network drives to drive letters
Map-myDrive is used to map network resources to local drive letters. It can use SmbMapping or PsDrive to 
do the mapping, but defaults to simple PsDrives for historical reasons. (SmbMapping was buggy when it 
was first introduced!)

Performs a standard drive mapping on the local machine using New-PsDrive syntax.
Map-MyDrive -SMB

Performs a standard drive mapping on the local machine using New-SmbMapping syntax.
Map-MyDrive -SMB -Force

Performs a standard drive mapping on the local machine using New-SmbMapping syntax, and
forces an unmapping of any existing drive maps before remapping.
Map-MyDrives -SMB -Force -AtHome $False

Performs a standard drive mapping on the local machine using New-SmbMapping syntax, and
forces an unmapping of any existing drive maps before remapping. The script assumes that 
it is NOT being run in my home domain, and therefore does only local mappings.
.Parameter SMB
When set, Map-myDrive uses SMB syntax to do the drive mappings
.Parameter Force
When set, Map-myDrive completely unmaps any existing drive mappings, and then remaps them. 
.Parameter AtHome
When True, Map-myDrive assumes that it has connectivity to the home domain resources. When
False, it assumes no home domain resources are available and maps to local shares. 
    Author: Charlie Russel
 Copyright: 2016 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 10 June, 2012
   ModHist: 26 June, 2012 A single, cleaner, call to GWMI
          : 26 April,2015 Cleaned up smb unmapping failures, and added Verbose
          : 27 June, 2015 New unmapping function
          : 04 Sept, 2016 Added AtHome Boolean to override environment, Force switch to force unmapping/remapping
Param ([Parameter(Mandatory=$false)][Switch]$SMB,

Write-Verbose "Running mapdrives.ps1 with SMB set to $SMB"
Write-Verbose "Athome is set to $AtHome"
Write-Verbose "Force is $Force"

$Psh = Get-Process PowerShell

# Start by checking for mapped drives
$DriveList = $Null
$PSDriveList = $Null
#$DriveList = Get-WMIObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 4 }

$DriveList = (Get-SMBMapping).LocalPath
write-verbose "The DriveList is: $DriveList"

if ($DriveList) {
   $PSDriveList = $DriveList -replace ":"
   write-verbose "The PSDrivelist is: $PSDriveList"

# Unmap any lingering ones
Function Remove-myMaps () {
   write-verbose "Removing drives in DriveList: $drivelist"
   Foreach ($drive in $DriveList) {
      $psDrive = $drive -replace ":" #remove unwanted colon from PSDrive name
      Write-Verbose "Running Remove-SmbMapping -LocalPath $Drive -Force -UpdateProfile"
      Remove-SmbMapping -LocalPath $Drive -Force -UpdateProfile 
      If ( (Get-PSDrive -Name $psDrive) 2>$Null ) {
      Write-Verbose "Running Remove-PsDrive -Name $psDrive -Force "
      Remove-PSDrive -Name $psDrive -Force
      write-host " "
      $DriveList = (Get-SMBMapping).LocalPath
      Write-Verbose "The drive list is now: $DriveList"

Function Map-myPSDrive () {
   New-PSDrive -Name I -root \\server\Install    -Scope Global -PSProv FileSystem -Persist
   New-PSDrive -Name J -root \\server\Download   -Scope Global -PSProv FileSystem -Persist
   New-PSDrive -Name S -root \\server\SharedDocs -Scope Global -PSProv FileSystem -Persist
   New-PSDrive -Name W -root \\server\Working    -Scope Global -PSProv FileSystem -Persist
   New-PSDrive -Name H -root \\server2\Download  -Scope Global -PSProv Filesystem -Persist
   New-PSDrive -Name K -root \\server2\Kindle    -Scope Global -PSProv FileSystem -Persist
   New-PSDrive -Name M -root \\server2\Music     -Scope Global -PSProv FileSystem -Persist
   New-PSDrive -Name N -root \\server2\Audible   -Scope Global -PSProv FileSystem -Persist
   New-PSDrive -Name P -root \\server2\Pictures  -Scope Global -PSProv FileSystem -Persist
   New-PSDrive -Name T -root \\labserver\Captures\70-742 -Scope Global -PSProv FileSystem -Persist

# Map using SMBMapping. It's more robust
Function New-mySMBMaps () {
   New-SmbMapping -LocalPath I: -RemotePath \\server\Install    -Persistent $True
   New-SmbMapping -LocalPath J: -RemotePath \\server\Download   -Persistent $True
   New-SmbMapping -LocalPath S: -RemotePath \\server\SharedDocs -Persistent $True
   New-SmbMapping -LocalPath W: -RemotePath \\server\Working    -Persistent $True
   New-SmbMapping -LocalPath H: -RemotePath \\server2\Downloads -Persistent $True
   New-SmbMapping -LocalPath K: -RemotePath \\server2\Kindle    -Persistent $True
   New-SmbMapping -LocalPath M: -RemotePath \\server2\Music     -Persistent $True
   New-SmbMapping -LocalPath N: -RemotePath \\server2\Audible   -Persistent $True
   New-SmbMapping -LocalPath P: -RemotePath \\server2\Pictures  -Persistent $True
   New-SmbMapping -LocalPath T: -RemotePath \\labserver\Captures\70-742 -Persistent $True

Function Map-myLocal () {
   New-SmbMapping -LocalPath K: -RemotePath \\localhost\Kindle          -Persistent $False  
   New-SmbMapping -LocalPath T: -RemotePath \\localhost\Captures\70-742 -Persistent $False  

if(! $PSDriveList ) {
   if ($Force) { Remove-myMaps }
   Write-Verbose "SMB is set to $SMB"
   if ($SMB) { 
      If ($AtHome) { 
      } else {
   } else { 
      If ($AtHome) { 
      } else {
} else { 
   # $Psh.count is the number of open PowerShell windows. I know that if it's less than or equal
   # to 2, then I haven't yet mapped the drives on both limited user and administrative windows. 
   # Therefore, we need to mapdrives here. Or, if I have run this with a -Force command, obviously.
   if (($Psh.count -le 2) -OR ($Force)) {
   Write-Verbose "SMB is set to $SMB"
   if ($SMB) { 
      If ($AtHome) { 
      } else {
   } else { 
      If ($AtHome) { 
      } else {

Setting Console Colours

As I described in my previous post, I always open both an admin and non-admin PowerShell window when I log on to a computer. To tell the two apart, I set the background colour of the Admin window to dark red, with white text, and the non-admin window to a white background with dark blue text. The result is clearly and immediately different, warning me when I’m running as an administrator. To do that, I use the following:

$id = [System.Security.Principal.WindowsIdentity]::GetCurrent() 
$p = New-Object$id)

# Find out if we're running as admin (IsInRole). 
# If we are, set $Admin = $True. 
if ($p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)){ 
   $Admin = $True 
} else { 
   $Admin = $False 

if ($Admin) { 
      $effectivename = "Administrator" 
   } else { 
      $effectivename = $ 


That works great for older versions of the Windows PowerShell console, but starting with PowerShell v5, that can have an unintended side effect. In PowerShell v5, we now have PSReadLine that does context sensitive colouration of the command line. But the default PowerShell console has a dark blue background, with white text. And when I changed the background colour of my non-admin PowerShell window to white, it gets a little hard to read!! So, to fix that, I use Set-PSReadLineOption to change the various kinds of context sensitive colour changes to something that works with a light background. We don’t want to do that for the dark red background of the Admin window, so we’ll need to check which colour we are and adjust accordingly.

First, get the current colour:

$pData = (Get-Host).PrivateData 
$curForeground = [console]::ForegroundColor 
$curBackground = [console]::BackgroundColor

You’ll only want to configure the context sensitive colouring options if you’re running on Windows 10 or Server 2016. Prior versions of Windows didn’t have the new system console that comes with Windows 10. So you’ll want to check that the build number is > 10240

$Build = (Get-WmiObject Win32_OperatingSystem).BuildNumber

If $Build -ge 10240, then set the various context sensitive tokens to work with the colour we have.

# PowerShell v5 uses PSReadLineOptions to do syntax highlighting. 
# Base the color scheme on the background color 
If ( $curBackground -eq "White" ) { 
      Set-PSReadLineOption -TokenKind None      -ForegroundColor DarkBlue  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Comment   -ForegroundColor DarkGray  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Keyword   -ForegroundColor DarkGreen -BackgroundColor White 
      Set-PSReadLineOption -TokenKind String    -ForegroundColor Blue      -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Operator  -ForegroundColor Black     -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Variable  -ForegroundColor DarkCyan  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Command   -ForegroundColor DarkRed   -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Parameter -ForegroundColor DarkGray  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Type      -ForegroundColor DarkGray  -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Number    -ForegroundColor Red       -BackgroundColor White 
      Set-PSReadLineOption -TokenKind Member    -ForegroundColor DarkBlue  -BackgroundColor White 
      $pData.ErrorForegroundColor   = "Red" 
      $pData.ErrorBackgroundColor   = "Gray" 
      $pData.WarningForegroundColor = "DarkMagenta" 
      $pData.WarningBackgroundColor = "White" 
      $pData.VerboseForegroundColor = "DarkYellow" 
      $pData.VerboseBackgroundColor = "DarkCyan" 
   } elseif ($curBackground -eq "DarkRed") { 
      Set-PSReadLineOption -TokenKind None      -ForegroundColor White    -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Comment   -ForegroundColor Gray     -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Keyword   -ForegroundColor Yellow   -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind String    -ForegroundColor Cyan     -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Operator  -ForegroundColor White    -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Variable  -ForegroundColor Green    -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Command   -ForegroundColor White    -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Parameter -ForegroundColor Gray     -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Type      -ForegroundColor Magenta  -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Number    -ForegroundColor Yellow   -BackgroundColor DarkRed 
      Set-PSReadLineOption -TokenKind Member    -ForegroundColor White    -BackgroundColor DarkRed 
      $pData.ErrorForegroundColor   = "Yellow" 
      $pData.ErrorBackgroundColor   = "DarkRed" 
      $pData.WarningForegroundColor = "Magenta" 
      $pData.WarningBackgroundColor = "DarkRed" 
      $pData.VerboseForegroundColor = "Cyan" 
      $pData.VerboseBackgroundColor = "DarkRed" 

Finally, let’s make sure that console window is the right size, and while we’re at it, set the window title. (This is a workaround for a PITA bug in recent builds of Windows 10/Server 2016 that seems to have problems setting the console window size and keeping it!)

$host.ui.rawui.WindowTitle = $effectivename + "@" + $HostName +" >" 
$Host.UI.RawUI.WindowSize = New-Object System.Management.Automation.Host.Size(120,40)


Now, with all of this, you have effective, context-sensitive, command-line colouring of your PowerShell windows.


ETA: The Console as we know it is completely broken in recent builds of Windows 10. As of August, 2018, you can’t effectively set the background color of the console and have it stick, plus they completely changed the behaviour of Set-PSReadlineOption by changing the parameters, and removing support for -TokenKind with FG/BG colors to totally break this script. Sigh.

More $Profile Tricks — Automatically Opening an Admin Window

I run as a limited user during my normal work, but I always keep one or more Admin windows open. These are logged in to my Domain Administrator account, running “As Administrator”. And I make sure I can tell that I’m running in that window by setting the colour scheme with a nice, dark red, background. Hard to miss! (I’ll show you how to do that in my next post. ) So, how do I do all that? Well, it starts by automatically opening a PowerShell window when I first log on, as described earlier here.

When that starts, I include code in my $Profile to first check how many PowerShell windows are already running, so I don’t start opening more if I don’t need them.

$PSH = Get-Process PowerShell

Simple, and let’s me get a count with $PSH.count. If this is the first PowerShell window I’ve opened ($PSH.count -lt 2) and this isn’t already an admin window, then I open an admin window. Let’s break this down: First, am I running as an Administrator?

# First, initialize $Admin to false 
$Admin = $False

# Then, find out who we are... 
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent() 
$p = New-Object$id)

# Find out if we're running as admin (IsInRole). 
if ($p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)){ 
   $Admin = $True 

Now, a function to run an elevated window as the domain administrator.

Function Start-myAdmin () { 
# Get admin credentials 
$AdminCred = Get-Credential 
        -UserName TreyResearch\Administrator ` 
        -Message "Enter your password for the Administrator account: " 
# Start an elevated window, but with designated creds. 
Start-Process PowerShell.exe ` 
       -Credential $AdminCred ` 
       -ArgumentList "Start-Process PowerShell.exe -Verb RunAs" ` 


Good. So we’re going to want to start that admin window automatically when we first logon. We do that by checking the count of open PowerShell windows ($PSH.count)

# If this isn't already an Admin window, and we don't have lots of other PowerShell 
# windows open, then we start an Admin PowerShell window. 
if ( ! $Admin  ) {
   cd $home
   if ($Psh.count -lt 2 ) {

Oh, and we’ll want an alias to be able to open up another one whenever we need it.

Set-Alias Admin -Value Start-myAdmin


Starting Windows PowerShell Automatically

Most system administrators routinely start a command line window of some sort when they log onto a computer. For me, obviously, that’s a PowerShell window. Actually, two PowerShell windows – one as a limited user, the other as an administrator, but that trick is for a different blog post. For this one, I’ll show you how to have Windows automatically start a PowerShell windows whenever you long on to your computer.

The key to this is the Run key in the registry. Specifically HKCU:\Software\Microsoft\Windows\CurrentVersion\Run. Programs listed in this key automatically start when the user (HKCU) logs on to the computer. So, all we need to do is insert a new value into the key for PowerShell. Easy enough, with New-ItemProperty. Here’s a function to do that:

Function Set-myPowerShellStart () {
   if (Get-ItemProperty HKCU:\Software\Microsoft\Windows\CurrentVersion\Run `
                        -Name "*PowerShell" 2>$NULL ) {
      write-verbose "Already an entry for starting PowerShell"
   } else {
      New-ItemProperty HKCU:\Software\Microsoft\Windows\CurrentVersion\Run `
                       -Name  "Windows PowerShell" `
                       -Value "C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell.exe"
      write-verbose "Added value for automatic PowerShell start"

Now, all you need to do is call that function, and it will insert a key in the registry, and the next time you log on to the computer, you’ll automatically have a PowerShell window open and ready to go.

A More Readable Path

The path on many modern computers gets to be so long that reading it is a nuisance. Worse, from within Windows PowerShell, there isn’t a built in equivalent to the old cmd “path” command. Yet all the information we need is there, it’s just not where we might expect it. The actual executable path for a PowerShell session is stored in $ENV:path. Here’s a quick function you put in your $profile to print out the PATH, one line for each item:

Function path { 

Simple, wasn’t it? Now, when you type “path” from the Windows PowerShell prompt, you’ll see something like this:

PSH> path 

Mapping Drives

My standard environment expects to have several drive mappings wherever I’m logged in to my network. Even when I’m running on a computer that isn’t joined to the domain. To facilitate that, I have a simple “mapdrives.cmd” file that has the necessary net use commands in it to map them.  Then my default PowerShell $profile calls mapdrives.cmd. This works well, except that the result is fairly messy if there’s a problem.

Problem? Sure. For example, if my laptop isn’t in the office, then it won’t be able to map the drives on my office network. Or, if the dries are already mapped as part of a group policy, I’ll get an error when I try to map them. So, I decided to get smarter about it. Here’s the relevant section of my $profile:

$InOffice = test-connection -quiet
$isMapped = Get-WMIObject -query “Select * from Win32_LogicalDisk where DeviceID=’A:'”
if ($InOffice -and ! $isMapped ){
if ($maps) {& $maps }

Now there may be even better ways to do this, but for me this works well. The $InOffice test checks for the presence of a server that should be reachable if I’m in the office, but that will not be reachable anywhere else. The $isMapped test checks to see if one of my standard drive mappings has already been done.

The result? If I’m not in the office, it won’t bother mapping drives and thus save some significant startup time while it tried to map them and then failed. And if I’m in the office, but they’ve already been mapped, it won’t bother either, saving no time but keeping my PowerShell window from echoing all those errors as it tries to map something that is already mapped.

Find this useful? Got a better way to do it? Leave a comment, please.



ETA: Yech, boy was this ugly back then. But now we have many new features in PowerShell v5, so I’ve updated things a bit. See my Mapping Drives Revisited post for a much better way to do this.