Setting the color and properties for all CMD and PowerShell windows based on admin / elevation status.
March 11, 2012 2 Comments
To quote Mark Twain, “The rumors of my death have been greatly exaggerated”.
This blog is not dead. I realize it’s been quite some time since my last post. Two factors have contributed to the lengthy hiatus. First, I’m busy. Not just “I wish I had some time to catch up on some television, movies, and books.” busy, but “I wonder just how few hours of sleep the human body actually needs to survive.” busy. Second, my goal with this blog is not just to aggregate what’s already out there, but to add unique value to this little corner of the internet. If I don’t have something useful to say that hasn’t been covered elsewhere I see no need to add to the din.
Now, on to the “value”!
Several years ago Aaron Margosis posted a way to make all your CMD windows running as admin “visually different” than your non-admin windows. You can (and should) read that blog here. Fast forward a few years and I find myself using PowerShell more and more. Furthermore, UAC makes it even more valuable to know which CMD and PowerShell windows are running with administrative credentials and which are not. Needless to say, as someone who has become accustomed to the functionality Aaron provided for CMD windows I wanted to port this same behavior to my PowerShell windows. Here is how I did it…
We’re all used to seeing the standard PowerShell console window. Nice, blue, boring. And that’s fine it does what it needs to do.
But what if we right-click PowerShell and “Run as administrator”? You get nearly the same thing. If you’re a point and click kind of person who doesn’t spend much time at the command line that may be fine for you. You’ve got one PowerShell console open, you know it’s running with administrative credentials because you just opened it, and you’re probably going to close it in a few moments when you’re done anyway. However, if you’re a command line junkie like me you’ve probably got a half-dozen console windows open across as many workstations and servers and keeping track of which are running with administrative credentials and which are not is impractical. I would much prefer to see something like this when running with administrative credentials.
PowerShell supports profile scripts that are loaded when the PowerShell session starts. I won’t go into details on profile scripts in this post but if you would like more information MSDN covers them here. We can use these profile scripts to automatically set the properties of the console window when PowerShell starts. The basic process is:
- Determine if the current PowerShell session has administrative credentials
- If the console is running with administrative credentials set the colors of the console to white text on a red background
- Set the prompt to use the standard “>” for non-administrators and “#” for administrators (a Cisco / *nix standard that I’ve grown accustomed to as well)
- If the console is running with administrative credentials output a warning to the user.
I’ll take each step in turn starting with the question “How the heck to I determine if my PowerShell session is running with administrative credentials?”. While there are a few ways to do this, the best answer is “Ask the system.”.
If($True -eq ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
If you’re not familiar with PowerShell and .NET that line might be a lot to digest. What we’re doing here is calling the GetCurrent static method of the WindowsIdentity class in the .NET framework (remember PowerShell is built on top of .NET), using the result to instantiate a WindowsPrincipal object, and calling the new object’s IsInRole method to see if it is in the Administrator role. If you don’t have a programming background and none of that made any sense to you not to fear, just know that we’re asking the .NET framework if the current user is an administrator.
The next step, setting the console color, is a bit more difficult that it would seem at first. It is a fairly simple matter to set the background and foreground colors of the PowerShell console with the commands
(Get-Host).UI.RawUI.BackgroundColor = "darkred" (Get-Host).UI.RawUI.ForegroundColor = "white"
However, this only sets the background and foreground colors from this point forward. What we want is to set the entire window to white text on a red background. To accomplish this we will
- Set the background and foreground colors as shown above
- Capture the existing screen buffer for later use
- Clear the screen (a side effect of which is to fill the screen with the current background color)
- Repaint the saved screen buffer back onto the screen with the new colors
To capture the existing screen buffer we’ll use
$bufferWidth = $host.ui.rawui.BufferSize.Width $bufferHeight = $host.ui.rawui.CursorPosition.Y $rec = new-object System.Management.Automation.Host.Rectangle 0,0,($bufferWidth - 1),$bufferHeight $buffer = $host.ui.rawui.GetBufferContents($rec)
These lines store the current width and height of the screen buffer (which we’ll also need later), then create a rectangle object matching the size of the screen buffer and use it to copy the contents of the entire screen buffer into the $buffer variable. With the buffer captured we can now clear the screen with
Clear-Host
which both clears the screen and fills it with the new background color.
Next we paint the screen buffer we captured back onto the console with the new background and foreground colors using
$YOffset = [console]::WindowTop for($i = 0; $i -lt $bufferHeight; $i++) { $bufferLine = New-Object System.Text.StringBuilder for($j = 0; $j -lt $bufferWidth; $j++) { $char = $buffer[$i,$j] $null = $bufferLine.Append($char.Character) } [console]::setcursorposition(0,$YOffset+$i) Write-Host $bufferLine.ToString() -NoNewline }
These nested loops step through each character in the $buffer variable building each line’s worth of characters into a single string using the .NET StringBuilder class and then output each reconstructed line to it’s appointed location in the console window using Write-Host. In case you’re wondering why I go to all the trouble of reconstructing lines from the $buffer variable it is because this method improves performance over painting each character directly to the output window.
To set the prompt to either use the “#” for a PowerShell session running with administrative credentials or a “>” if the session is not running with administrative credentials we will set a global variable in the appropriate branch or our If statement for later use. Depending on the branch either
$GLOBAL:PromptTrail = "#"
Or
$GLOBAL:PromptTrail = ">"
PromptTrail is set as a Global variable because, as you will see in the next step, we will be using it outside of the current scope. To complete the changes to the prompt we must redefine the Prompt function with
Function Prompt { Write-Output $("PS " + $(Get-Location) + $GLOBAL:PromptTrail + " ") -NoNewline }
The final, and probably most straightforward step is to output a warning to the console if the PowerShell session is running with administrative credentials. To do so we’ll use the same test we used previously along with Write-Output.
If($True -eq ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Output "" Write-Output "CAUTION: PowerShell is running with administrator credentials!" Write-Output "" Write-Output "" }
Writing the warning to the console is done last so that it re-writes the prompt after the Prompt function has been redefined. We can thus (ab)use PowerShell’s internal buffer management to remove the old prompt and paint the new prompt for us during the Write-Output operations.
But wait there’s more! If you simply copy your profile.ps1 script into %windir%\system32\WindowsPowerShell\v1.0\ on a system with a default installation of PowerShell you’ll get a nasty error each time you open PowerShell and the console won’t change as expected. This happens because to PowerShell your profile script is just like any other script and the default behavior for PowerShell is to not run any scripts. You can change this behavior using Set-ExecutionPolicy (as admin). I would highly recommend against a setting of Unrestricted. The safe bet, that still allows you to use a profile script, is to take the time so sign your profile.ps1 script and use the AllSigned execution policy. However, even this setting comes with some risk so be sure to read and understand how PowerShell execution policies work. For more information open PowerShell and see Get-Help Set-ExecutionPolicy and Get-Help About_Execution_Policies.
Also note that there are 32 bit and 64 bit versions of PowerShell. If you’re attempting this setup on a 64 bit system you will need to make a copy of profile.ps1 in the appropriate location in both the System32 and the SysWow64 folders. You will also need to set the execution policies for both 32 and 64 bit environments.
Hopefully you find this information useful. For convenience I’ve attached the complete profile.ps1. The included script does not contain a signature because in my environment it is signed by an internally trusted CA so it wouldn’t do you any good. Beside now that you know the potential of profile scripts you’ll likely want to expand and improve the included script to suit your taste anyway. If you need help with self signing your profile script Scott Hanselman has an excellent walkthrough.
As a bonus here is the equivalent registry command to mimic the behavior in cmd.exe using Arron’s original technique. Note that it is wrapped to several lines here for ease of reading but should be a single line when entered (or copied) into the console. Again, on x64 platforms you’ll need to do this twice, once in the native 64 bit environment and once in the 32 bit WoW64 environment.
REG ADD "HKLM\SOFTWARE\Microsoft\Command Processor" /v AutoRun /d "reg query HKU\S-1-5-19 >nul 2>nul && (color 4F && PROMPT $P# && ECHO. && ECHO CAUTION: Windows Command Processor is running with administrator credentials!) || (color 07 && PROMPT $P$G)" /f
Download: profile.ps1(.txt)
Warning: A return code issue may occur if using regedit, bcdedit or other commands that return non-zero exit code. You may not be able to run some install or maintenance scripts correctly. For exact example see the link.
https://stackoverflow.com/questions/48543292/exit-b-0-returns-1-in-process-exitvalue-if-run-as-non-admin-on-windows-server-2
You are correct, in fact I’ve experienced this exact issue in rare cases. Specifically with the MS Office Installer. However, you can easily work around this issue by explicitly calling cmd.exe and adding /D switch.