Modifying IE Compatibility View Settings with Powershell

I recently upgraded my workstation to Windows 8.1 and as such, am now using Internet Explorer 11. While there are some welcomed improvements, there are several changes that have made day-to-day administration activities a bit challenging. For instance, all of the Dell hardware we use has a Remote Access Controller installed that allows us to perform various remote administration tasks. Unfortunately, the current version of firmware for these DRACs is not compatible with IE 11. However, running in IE 7 compatibility mode allows the UI of the DRACs to function properly.

The problem is, we access all of these directly by private IP and adding hundreds of IP addresses to the IE compatibility view settings on multiple workstations is a bit of a pain. Thankfully, these compatibility view exceptions can be set with Group Policy, but the workstations I use are in a Workgroup and do not belong to a domain. I set out to find a way to programmatically add these exceptions using powershell.

First, it’s important to note that the registry keys that control this behavior changed in IE 11. In previous versions of IE, the setting was exclusively maintained under HKCU(HKLM)\Software\[Wow6432Node]\Policies\Microsoft\Internet Explorer\BrowserEmulation\PolicyList. Starting with IE 11, there’s an additional key under HKCU\Software\Microsoft\Internet Explorer\BrowserEmulation\ClearableListData\UserFilter that controls compatibility view settings. Unfortunately, this new key is stored in binary format and there’s not much information regarding it. I was able to find a stackoverflow post where a user attempted to decipher the data, but I found that some of the assumptions they made did not hold true. Via a process of trial-and-error, I was able to come up with a script that can set this value. However, because IE 11 still supports the previous registry key, I HIGHLY recommend using the other method described later in this post. While this seems to work, there are several values I was not able to decode.

The script will pipe the values in the $domains array into the UserFilter registry key. It accepts either top-level domains, IP addresses or Subnets in CIDR notation.

$key = "HKCU:\Software\Microsoft\Internet Explorer\BrowserEmulation\ClearableListData"
$item = "UserFilter"
 
. .\Get-IPrange.ps1
$cidr = "^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)/(3[0-2]|[1-2]?[0-9])$"
 
[byte[]] $regbinary = @()
 
#This seems constant
[byte[]] $header = 0x41,0x1F,0x00,0x00,0x53,0x08,0xAD,0xBA
 
#This appears to be some internal value delimeter
[byte[]] $delim_a = 0x01,0x00,0x00,0x00
 
#This appears to separate entries
[byte[]] $delim_b = 0x0C,0x00,0x00,0x00
 
#This is some sort of checksum, but this value seems to work
[byte[]] $checksum = 0xFF,0xFF,0xFF,0xFF
 
#This could be some sort of timestamp for each entry ending with 0x01, but setting to this value seems to work
[byte[]] $filler = 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01
 
#Examples: mydomain.com, 192.168.1.0/24
$domains = @("google.com","192.168.1.0/24")
 
function Get-DomainEntry($domain) {
[byte[]] $tmpbinary = @()
 
[byte[]] $length = [BitConverter]::GetBytes([int16]$domain.Length)
[byte[]] $data = [System.Text.Encoding]::Unicode.GetBytes($domain)
 
$tmpbinary += $delim_b
$tmpbinary += $filler
$tmpbinary += $delim_a
$tmpbinary += $length
$tmpbinary += $data
 
return $tmpbinary
}
 
if($domains.Length -gt 0) {
[int32] $count = $domains.Length
 
[byte[]] $entries = @()
 
foreach($domain in $domains) {
if($domain -match $cidr) {
$network = $domain.Split("/")[0]
$subnet = $domain.Split("/")[1]
$ips = Get-IPrange -ip $network -cidr $subnet
$ips | %{$entries += Get-DomainEntry $_}
$count = $count - 1 + $ips.Length
}
else {
$entries += Get-DomainEntry $domain
}
}
 
$regbinary = $header
$regbinary += [byte[]] [BitConverter]::GetBytes($count)
$regbinary += $checksum
$regbinary += $delim_a
$regbinary += [byte[]] [BitConverter]::GetBytes($count)
$regbinary += $entries
}
 
Set-ItemProperty -Path $key -Name $item -Value $regbinary

You’ll need the Get-IPrange.ps1 script from the technet gallery and you can download the above script here: IE11_CV

IE 11 still supports the older registry key, therefore it is the preferred method not only because the above is a hack, but also because the data is stored in the registry as strings and it supports specific hosts instead of only top-level domains. Again, this script supports hosts, domains, IP Addresses and Subnets in CIDR notation.

 
$key = "HKLM:\SOFTWARE\Wow6432Node\Policies\Microsoft"
 
if(-Not (Test-Path "$key\Internet Explorer")) {
New-Item -Path $key -Name "Internet Explorer" | Out-Null
}
 
if(-Not (Test-Path "$key\Internet Explorer\BrowserEmulation")) {
New-Item -Path "$key\Internet Explorer" -Name "BrowserEmulation" | Out-Null
}
 
if(-Not (Test-Path "$key\Internet Explorer\BrowserEmulation\PolicyList")) {
New-Item -Path "$key\Internet Explorer\BrowserEmulation" -Name "PolicyList" | Out-Null
}
 
. .\Get-IPrange.ps1
$cidr = "^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)/(3[0-2]|[1-2]?[0-9])$"
 
#Examples: mydomain.com, 192.168.1.0/24
$domains = @("google.com","192.168.1.0/24")
 
$regkey = "$key\Internet Explorer\BrowserEmulation\PolicyList"
 
foreach($domain in $domains) {
if($domain -match $cidr) {
$network = $domain.Split("/")[0]
$subnet = $domain.Split("/")[1]
$ips = Get-IPrange -ip $network -cidr $subnet
$ips | %{$val = New-ItemProperty -Path $regkey -Name $_ -Value $_ -PropertyType String | Out-Null}
$count = $count - 1 + $ips.Length
}
else {
New-ItemProperty -Path $regkey -Name $domain -Value $domain -PropertyType String | Out-Null
}
}

Again, you’ll need the Get-IPrange.ps1 script from the technet gallery and you can download the above script here: IE_CV

Powershell Remoting

One of the most powerful features of Powershell is the ability to issue commands to remote systems, better known as Powershell remoting. This further lessens the need for console or remote desktop access to administer systems. Powershell remoting was introduced in v2 and relies on the Windows Remote Management service (WinRM) to issue commands to remote systems.

Enabling PowerShell remoting is fairly simple. For a single system on a domain, you can run the Enable-PSRemoting -Force cmdlet which will perform necessary configuration steps. You can also ensure WinRM is configured properly using the quickconfig command:

winrm quickconfig

You can use Group Policy if you need to enable remoting on multiple systems. Do this by automatically configuring the WinRM service:

Computer Configuration\Policies\Administrative Templates\Windows Components\Windows Remote Management (WinRM)\WinRM Service\Allow automatic configuration of listeners

Enable this GPO setting and use * for IPv4 and IPv6 filters (unless you wish to limit WinRM requests to specific source IP ranges). In a non-domain environment, you will need to add the remote system to the TrustedHosts list on the client system:

winrm s winrm/config/client ‘@{TrustedHosts=”RemoteSystem”}’

Alternatively, for non-domain environments, you can configure WinRM to allow SSL connections. You will need a “Server Authentication” certificate installed in the local computer certificate store, with a CN matching the hostname that is not expired, revoked, or self-signed. WinRM then needs to be configured for HTTPS using the certificate:

winrm quickconfig -transport:https

To check the current WinRM configuration, use the get command:

winrm get winrm/config

Once WinRM is configured properly, ensure there is a firewall rule in the local Windows Firewall allowing inbound traffic on port 5985 (and port 5986 for SSL). If WinRM is not properly configured, or there is a firewall blocking traffic, you will likely see an error similar to this:

[RemoteSystem] Connecting to remote server RemoteSystem failed with the following error message : WinRM cannot complete the operation. Verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled and allows access from this computer. By default, the WinRM firewall exception for public profiles limits access to remote computers within the same local subnet. For more information, see the about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (RemoteSystem:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : WinRMOperationTimeout,PSSessionStateBroken

Test Powershell remoting with the Test-WSMan cmdlet:

Test-WSMan -Computer RemoteSystem

Which will output something similar to this:

wsmid : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor : Microsoft Corporation
ProductVersion : OS: 0.0.0 SP: 0.0 Stack: 2.0

Now that WinRM is configured properly, we can open a remote powershell session from the client system:

Enter-PSSession -ComputerName RemoteSystem -Credential username

The credential parameter is optional on domain-joined systems but can be used to open a remote Powershell session as a specific user. If the remote system cannot authenticate the client because it is not domain joined and you have not added it to the TrustedHosts list, you will receive an error similar to this:

Enter-PSSession : Connecting to remote server RemoteSystem failed with the following error message : The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config. For more information, see the about_Remote_Troubleshooting Help topic.
At line:1 char:1
+ Enter-PSSession -ComputerName RemoteSystem -Credential jeff
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (RemoteSystem:String) [Enter-PSSession], PSRemotingTransportException
+ FullyQualifiedErrorId : CreateRemoteRunspaceFailed

You can also run commands on several remote systems simultaneously (this example will force a checkin with the WSUS server):

Invoke-Command -ComputerName RemoteSystem1, RemoteSystem2 -ScriptBlock {wuauclt /reportnow}

Happy Remoting!

Compare Two Directories with Powershell

We use DFS to keep webfarm nodes’ content in sync and ran across a problem where a directory with thousands of files and folders had one missing file on a replica. Here’s a quick script we used to find out what files were missing:

$dir1 = “\\server1\c$\folder1”
$dir2 = “\\server2\c$\folder1”
$d1 = get-childitem -path $dir1 -recurse
$d2 = get-childitem -path $dir2 -recurse
$results = @(compare-object $d1 $d2)

foreach($result in $results)
{
$result.InputObject
}

 

Directory: \\server1\c$\folder1\subfolder

Mode LastWriteTime Length Name
—- ————- —— —-
-a— 2/16/2012 4:08 PM 162 ~$README.txt

This will output all of the files and directories that exist in only one location.

Read & Write .NET machine key with Powershell

We’re putting together webfarms all the time at OrcsWeb, and one of the cardinal rules with webfarms is that all systems need to have matching settings. To help automate this process, I put together a quick Powershell script that will read/write the machine key to the root machine.config files for all versions of the .NET framework.

machineKeys.ps1

# Machine Key Script
#
# Version 1.0
# 6/5/2012
#
# Jeff Graves
#
param ($readWrite = "read", $allkeys = $true, $version, $validationKey, $decryptionkey, $validation)
 
function GenKey ([int] $keylen) {
	$buff = new-object "System.Byte[]" $keylen
	$rnd = new-object System.Security.Cryptography.RNGCryptoServiceProvider
	$rnd.GetBytes($buff)
	$result =""
	for($i=0; $i -lt $keylen; $i++)	{
		$result += [System.String]::Format("{0:X2}",$buff[$i])
	}
	$result
}
 
function SetKey ([string] $version, [string] $validationKey, [string] $decryptionKey, [string] $validation) {
    write-host "Setting machineKey for $version"
    $currentDate = (get-date).tostring("mm_dd_yyyy-hh_mm_s") # month_day_year - hours_mins_seconds
 
    $machineConfig = $netfx[$version]
 
    if (Test-Path $machineConfig) {
        $xml = [xml](get-content $machineConfig)
        $xml.Save($machineConfig + "_$currentDate")
        $root = $xml.get_DocumentElement()
        $system_web = $root."system.web"
        if ($system_web.machineKey -eq $nul) { 
        	$machineKey = $xml.CreateElement("machineKey") 
        	$a = $system_web.AppendChild($machineKey)
        }
        $system_web.SelectSingleNode("machineKey").SetAttribute("validationKey","$validationKey")
        $system_web.SelectSingleNode("machineKey").SetAttribute("decryptionKey","$decryptionKey")
        $system_web.SelectSingleNode("machineKey").SetAttribute("validation","$validation")
        $a = $xml.Save($machineConfig)
    }
    else { write-host "$version is not installed on this machine" -fore yellow }
}
 
function GetKey ([string] $version) { 
    write-host "Getting machineKey for $version"
    $machineConfig = $netfx[$version]
 
    if (Test-Path $machineConfig) { 
        $machineConfig = $netfx.Get_Item($version)
        $xml = [xml](get-content $machineConfig)
        $root = $xml.get_DocumentElement()
        $system_web = $root."system.web"
        if ($system_web.machineKey -eq $nul) { 
        	write-host "machineKey is null for $version" -fore red
        }
        else {
            write-host "Validation Key: $($system_web.SelectSingleNode("machineKey").GetAttribute("validationKey"))" -fore green
    	    write-host "Decryption Key: $($system_web.SelectSingleNode("machineKey").GetAttribute("decryptionKey"))" -fore green
            write-host "Validation: $($system_web.SelectSingleNode("machineKey").GetAttribute("validation"))" -fore green
        }
    }
    else { write-host "$version is not installed on this machine" -fore yellow }
}
 
$global:netfx = @{"1.1x86" = "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\CONFIG\machine.config"; `
           "2.0x86" = "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG\machine.config"; `
           "4.0x86" = "C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\CONFIG\machine.config"; `
           "2.0x64" = "C:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\CONFIG\machine.config"; `
           "4.0x64" = "C:\WINDOWS\Microsoft.NET\Framework64\v4.0.30319\CONFIG\machine.config"}
if(!$allkeys)
{
    while(!$version) {
        $input = read-host "Version (1.1x86, 2.0x86, 4.0x86, 2.0x64, 4.0x64)"
        if ($netfx.ContainsKey($input)) { $version = $input }
    }
}
 
if ($readWrite -eq "read")
{
    if($allkeys) {
        foreach ($key in $netfx.Keys) { GetKey -version $key }
    }
    else {
        GetKey -version $version
    }
}
elseif ($readWrite -eq "write")
{   
    if (!$validationkey) {
    	$validationkey = GenKey -keylen 64
    	write-host "Validation Key: $validationKey" -fore green
    }
 
    if (!$decryptionkey) {
    	$decryptionKey = GenKey -keylen 24
    	write-host "Decryption Key: $decryptionKey" -fore green
    }
 
    if (!$validation) {
    	$validation = "SHA1"
    	write-host "Validation: $validation" -fore green
    }
 
    if($allkeys) {
        foreach ($key in $netfx.Keys) { SetKey -version $key -validationkey $validationkey -decryptionKey $decryptionKey -validation $validation}
    }
    else {
        SetKey -version $version -validationkey $validationkey -decryptionKey $decryptionKey -validation $validation
    }
}

 

Multithreaded Powershell Port Scanner

We recently had to perform a hardware upgrade of a perimeter firewall. Doing so is a major undertaking, and while we have very good documentation, it’s always important to do some real-world testing.

To facilitate this, we needed to perform some port scanning from outside our network to ensure that A) All of our firewall rule documentation matched what was actually configured, and B) Ensure a smooth transition to the new hardware. Most port scanners I found were capable of scanning a port range for a given IP set. But I wasn’t able to find much of anything that could take specific IP/port data and return the results. I had previously written a simple ASP.NET application to do this, but it wasn’t designed for testing large datasets.

ASP.NET Port Scanner

So, I decided Powershell was the best bet. There were several available examples, but nothing that truly did what we needed. I was able to pull several resources together and came up with the attached Powershell script. Credit for the port detection goes to Boe Prox, to Gaurhoth for the IP range powershell functions, and to Oisin Grehan for the multithreading code.

The result is a script that takes a CSV input and outputs the results to CSV. You can specify IP addresses (eg. 192.168.100.1), CIDR subnets (192.168.1.0/24, 10.254.254.16/28, and/or IP ranges (10.0.1.1-10). The services.xml file in the bin folder contains a powershell object with port settings for various well-know ports and can be modified to meet your needs. Port cans be specified using their well-known name (eg. SMTP, RDP, HTTP) or in a protocol/portNum format (eg. tcp/80, udp/53, tcp/4900-4910).

Scanning is fairly quick:

PS D:\temp\portscanner> .\PortScanner.ps1 Importing Data from .\externalrules.csv
Imported 3033 targets
Flattening targets into endpoints
There are 3996 to scan
Begin Scanning at 07/08/2011 15:58:31
Waiting for scanning threads to finish...
We scanned 3996 endpoints in 399.1811698
Exporting data to .\results.csv

Happy networking!

portscanner