Monthly Archives: May 2016

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 system.security.principal.windowsprincipal($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" 
      $host.UI.RawUI.Backgroundcolor="DarkRed" 
      $host.UI.RawUI.Foregroundcolor="White" 
      clear-host 
   } else { 
      $effectivename = $id.name 
      $host.UI.RawUI.Backgroundcolor="White" 
      $host.UI.RawUI.Foregroundcolor="DarkBlue" 
      clear-host 
}

 

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.

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 system.security.principal.windowsprincipal($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" ` 
       -NoNewWindow

}

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 ) {
      Start-myAdmin
   }
}

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

 

Clearing the Archive Attribute Bit

I use a whole set of PowerShell scripts to create my lab environment when I’m working on a book or article. The master scripts all reside in a Build directory on my labhost. The problem is that when something goes wonky, or I need to tweak a script to do something a bit different, I’m usually working from the lab client, not the host. What I don’t want to do is lose any changes I’ve made if I blow away a client (which happens fairly often in a lab!) So I try to remember to copy the change up to the master Build directory. But that’s not 100%, so I wanted a quick way to copy only the changed files back up to the host. The easy way to do it should be using the filesystem’s Archive attribute bit, but it turns out Windows PowerShell is just plain brain-dead about how it handles file attributes. You have to resort to binary ANDs and binary XORs to manipulate them. Well, that’s just not fun. So, having spent the time to get it working, I thought I’d just share the script I use to clear the Archive attribute for one or many files. Enjoy.

<# 
.Synopsis 
Clears the archive bit on a set of files 
.Description 
Clear-myArchiveBit returns a list of files whose archive bit is set to on, and resets 
them to off. This is command supports the -Recurse parameter. 
.Example 
Clear-myArchiveBit *.ps1 
This command clears the archive bits of all ps1 files in the current directory

.Example 
Clear-myArchiveBit *.ps1 -Path $home\psbin -recurse 
This command clears the archive bit of all PS1 files in the $home\psbin directory, 
and all subdirectories of $home\psbin.

.Parameter Filter 
The filename filter to use. Aliases are Name and Filename. Default is *. 
.Parameter Path 
The path to the files to be changed. Default is ".". 
.Parameter Recurse 
If specified, the change is made to the Path and all subdirectories of the Path. 
.Inputs 
[string] 
[string] 
[switch] 
.Notes 
    Author: Charlie Russel 
 Copyright: 2016 by Charlie Russel 
          : Permission to use is granted but attribution is appreciated 
   Initial: 16 May, 2016 (cpr) 
   ModHist: 
          : 
#> 
[CmdletBinding()] 
Param( 
     [Parameter(Mandatory=$False,ValueFromPipeline=$True,Position=0)] 
     [alias("Name","Filename")] 
     [string]$Filter = "*", 
     [Parameter(Mandatory=$false,ValueFromPipeline=$false,Position=1)] 
     [string]$Path = ".", 
     [Parameter(Mandatory=$false,ValueFromPipeline=$false)] 
     [switch]$Recurse 
     ) 
Write-Verbose "Clearing the Archive bit on $filter in $path" 
# This is how we'll XOR the bit when we get to that. 
$Archive = [io.fileattributes]::Archive

# First, get all the files whose Archive bit is currently set. 
if ($Recurse) { 
   $chgdFiles = Get-ChildItem -Filter $Filter -Path $path -Recurse ` 
                  | Where {$_.mode -match "a" } 
} else { 
   $chgdFiles = Get-ChildItem -Filter $Filter -Path $path ` 
                  | Where {$_.mode -match "a" } 
}

# Now, we clear the bit on those files using a binary XOR. 
ForEach ($file in $chgdFiles ) { 
   Set-ItemProperty -Path $file.FullName ` 
                    -Name Attributes ` 
                    -Value ((Get-ItemProperty $file.FullName).Attributes ` 
                            -bXOR $Archive ) 
} 
Write-Verbose "The following files have had their Archive attribute cleared: " 
Write-Verbose "" 
$chgdFiles | Write-Verbose

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.

Promoting a new domain controller

I’ve been working with Windows Server 2016 CTP5 recently, and because I installed it without the Desktop Experience (what we used to call a Server Core installation), I’m having to do everything in Windows PowerShell. No complaints, I enjoy it, but it does force me to think about things a bit sometimes.

One of the tasks I needed to do was promote a new server to be a secondary domain controller. The PowerShell command for this is: Install-ADDSDomainController. But before you start promoting a new DC, it’s a really good idea to test that the promotion will succeed by using the Test-ADDSDomainControllerInstallation cmdlet. So, I combined the two steps into a simple script that allows you to run the test, and if the output looks clean, finish the installation and initiate a reboot.

The script is smart enough to realize you haven’t installed the ActiveDirectory feature yet, and goes ahead and installs it for you.

<# 
.Synopsis 
Tests a candidate domain controller, and then promotes it to DC.

.Description 
Promote-myDC first tests if a domain controller can be successfully promoted, and, 
if the user confirms that the test was successful, completes the promotion and 
restarts the new domain controller. 

.Example 
Promote-myDC -Domain TreyResearch.net

Tests if the local server can be promoted to domain controller for the 
domain TreyResearch.net. The user is prompted after the test completes 
and must press the Y key to continue the promotion. 

.Parameter Domain 
The domain to which the server will be promoted to domain controller. 

.Inputs 
[string] 

.Notes 
    Author: Charlie Russel 
 Copyright: 2016 by Charlie Russel 
          : Permission to use is granted but attribution is appreciated 
   Initial: 05/14/2016 (cpr) 
   ModHist: 
          : 
#> 
[CmdletBinding()] 
Param( 
     [Parameter(Mandatory=$True,Position=0)] 
     [string] 
     $Domain 
     )

Write-Verbose "Testing if ADDSDeployment module is available" 
If ( ! (Get-Module ADDSDeployment )) { 
   Write-Verbose "Installing the ActiveDirectory Windows Feature, since you seem to have forgotten that." 
   Install-WindowsFeature -Name ActiveDirectory -IncludeManagementTools 
   Write-Host "" 
}

If ( ! (Get-Module ADDSDeployment )) { 
   throw "Failed to install the ActiveDirectory Windows Feature." 
}

Write-Verbose "Testing if server $env:computername can be promoted to DC in the $Domain domain" 
Write-Host "" 
Test-ADDSDomainControllerInstallation ` 
      -NoGlobalCatalog:$false ` 
      -CreateDnsDelegation:$false ` 
      -CriticalReplicationOnly:$false ` 
      -DatabasePath "C:\Windows\NTDS" ` 
      -DomainName $Domain ` 
      -LogPath "C:\Windows\NTDS" ` 
      -NoRebootOnCompletion:$false ` 
      -SiteName "Default-First-Site-Name" ` 
      -SysvolPath "C:\Windows\SYSVOL" ` 
      -InstallDns:$true ` 
      -Force 
Write-Host "" 
Write-Host "" 
Write-Host ""

Write-Host -NoNewLine "If the above looks correct, press Y to continue...  " 
$Key = [console]::ReadKey($true) 
$sKey = $key.key

Write-Verbose "The $sKey key was pressed." 
Write-Host "" 
Write-Host "" 
If ( $sKey -eq "Y" ) { 
   Write-Host "The $sKey key was pressed, so proceeding with promotion of $env:computername to domain controller." 
   Write-Host "" 
   sleep 5 
   Install-ADDSDomainController ` 
      -SkipPreChecks ` 
      -NoGlobalCatalog:$false ` 
      -CreateDnsDelegation:$false ` 
      -CriticalReplicationOnly:$false ` 
      -DatabasePath "C:\Windows\NTDS" ` 
      -DomainName $Domain ` 
      -InstallDns:$true ` 
      -LogPath "C:\Windows\NTDS" ` 
      -NoRebootOnCompletion:$false ` 
      -SiteName "Default-First-Site-Name" ` 
      -SysvolPath "C:\Windows\SYSVOL" ` 
      -Force:$true 
} else { 
   Write-Host "The $sKey key was pressed, exiting to allow you to fix the problem." 
   Write-Host "" 
   Write-Host "" 
}