Windows 7 SP1 – Slow Windows Updates

As an MCITP I’m well-versed in Group Policy and what not, but building a clean Windows base image with all updates applied and some prerequisite software has always always been a relatively manual, inconsistent, and annoying process. I recently did a major overhaul of our Operations Network/Server Infrastructure and figured there’s no better time to give my trusty Chef, Packer & Vagrant tool set a shot with our workstations.

Currently, I’ve got my OS X & *nix infrastructure under the control of Chef, and I’ve got e2e OS build & deployment processes in place (Packer -> Vagrant -> Chef ). As a well-versed, cross-platform expert, I figured it was about time to bring some of that over to the Windows side of our operation. Leveraging a DevOps skill set can cut back on the need for IT support personnel by automating trivial IT processes. In my opinion, anything that can be automated, should be automated… It is more efficient no matter which way you look at it.

I noticed that Windows 7 SP1 in particular seems to consume a TON of resources when looking for updates; this issue can literally cost you hours… It seems Microsoft has addressed this with KB3102810.

Here is a PowerShell function which you can throw in your provisioning script to tackle this issue.

Function Fix-WinUpdate {
  # Retrieve Operating System Information
  $OSInfo=(Get-WmiObject -Class Win32_OperatingSystem)
  # FixUp Windows 7 SP1 & Server 2008 R2
  If (($OSInfo).Version -eq '6.1.7601') {
    If ([System.IntPtr]::Size -eq 4) { $osarch='x86' } else { $osarch='x64' }
    If (($OSInfo).ProductType -eq 1) {
      Write-Host 'Windows 7 SP1 Detected' -ForegroundColor 'Green'
      $WUHotFixUrl = "https://download.microsoft.com/download/F/A/A/FAABD5C2-4600-45F8-96F1-B25B137E3C87/Windows6.1-KB3102810-${osarch}.msu"
    }
    Else
    {
      Write-Host 'Windows Server 2008 R2 Detected' -ForegroundColor 'Green'
      $WUHotFixURL = 'https://download.microsoft.com/download/1/E/1/1E1E4FBD-9E64-4AF5-9494-592605CAE5D6/Windows6.1-KB3102810-x64.msu'
    }

    $WUHotFix = "C:\Windows\Temp\$($WUHotFixURL.Substring($WUHotFixURL.LastIndexOf('/') + 1))"

    # Download & Install Latest Windows Update Client
    If (!(Get-HotFix -Id 'KB3102810' -ErrorAction SilentlyContinue)) {
      Write-Host "Downloading $(${OSInfo}.Caption) WU Client Update (KB3102810)"
      (new-object net.webclient).DownloadFile($WUHotFixUrl, $WUHotFix)
      Write-Host "Installing $(${OSInfo}.Caption) WU Client Update (KB3102810)"
      Start-Process wusa -ArgumentList "${WUHotFix}", '/quiet' -Wait
      Write-Host 'This update requires a reboot'
      Restart-Host
    }
  }
    # Windows Management Instrumentation Framework
    $WMIUpdateUrl = "https://download.microsoft.com/download/3/D/6/3D61D262-8549-4769-A660-230B67E15B25/Windows6.1-KB2819745-${osarch}-MultiPkg.msu"
    $WMIUpdate = "C:\Windows\Temp\$($WMIUpdateUrl.Substring($WMIUpdateUrl.LastIndexOf('/') + 1))"

    # Download & Install Windows Management Instrumentation Framework 4.0
    If (Test-Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client')
    {
      If (!(Get-HotFix -Id 'KB2819745' -ErrorAction SilentlyContinue)) {
        Write-Host "Downloading $(${OSInfo}.Caption) WMI 4.0 (KB2819745)"
        (new-object net.webclient).DownloadFile($WMIUpdateUrl, $WMIUpdate)
        Write-Host "Installing $(${OSInfo}.Caption) WMI 4.0 (KB2819745)"
        Start-Process wusa -ArgumentList "${WMIUpdate}", '/quiet' -Wait
        Write-Host 'This update requires a reboot'
        Restart-Host
      }
    }
  }
}
Advertisements

AWS – Globally Adjusting ELB SSL Policy

A while back, I had to adjust the policy of all Elastic Load Balancers in my organization to disable SSLv3 due to the POODLE exploit. This can be an error-prone task if done by hand, especially if your architecture spans multiple regions and/or more than a handful of ELBs. The nice thing about cloud architecture, is that nearly everything can be automated and/or scripted.  For that reason, I went ahead and wrote a PowerShell script to handle this.

Most other write-ups I have seen do not take into account Stickiness policies, which are also applied to listeners.  If you run the Set-ELBLoadBalancerPolicyOfListener cmdlet with only an SSL policy applied, it will remove any other existing listener policies.  It is important to check the ELB for other policies, and make sure they are reapplied.  There is logic in this script that handles that.

It is important to note that this script makes use of Amazon’s template SSL Negotation Policies, but could be adapted to make use of your own.

As of 4/9/15, you cannot simply set the ELB policy to a newer reference policy, although AWS documentation states otherwise.  For this reason, a new policy must be created which references the AWS Reference-Security-Policy of choice.  You can retrieve a list of available reference policies with the Get-ELBLoadBalancerPolicy cmdlet.

Code also available on my GitHub

# AWS Global ELB SSL Policy
# Brian Dwyer - Intelligent Digital Services - 4/5/15

# Variables
$PolicyName="SSL-POLICY--$(Get-Date -Format yy-MM-ddTHHmmss)"
$ELBReferencePolicy='ELBSecurityPolicy-2015-03'

# Dependencies
Import-Module AWSPowerShell



Write-Host 'Finding AWS Regions containing ELBs...'

$RegionsWithELBs = @{}

ForEach ( $region in (Get-EC2Region).RegionName )
{
    $ELB_Count = (Get-ELBLoadBalancer -Region $region).count
    if ( $ELB_Count -ge 1 )
    {
        $RegionsWithELBs.Add($region, $ELB_Count)
    }
}

# Display ELB Regions & Count
$tformat = @{Expression={$_.Name};Label="Region"}, @{Expression={$_.Value};Label="ELB Count"}
$RegionsWithELBs.GetEnumerator() | Sort-Object Value -Descending | Format-Table $tformat -AutoSize




ForEach ( $region in $RegionsWithELBs.Keys )
{
    # Verify reference policy existence in region
    if ( (Get-ELBLoadBalancerPolicy -Region $region).PolicyName -contains $ELBReferencePolicy )
    {
        Write-Host "`nModifying ELBs in region: '$region' `n"

        # Loop through the ELBs
        ForEach ( $lb in (Get-ELBLoadBalancer -Region $region ).LoadBalancerName )
        {
            # Verify ELB serves HTTPS
            if ( (Get-ELBLoadBalancer -Region $region -LoadBalancerName $lb).ListenerDescriptions.Listener.Protocol -contains 'HTTPS' )
            {

                # Find Existing Policies (App/Cookie Stickiness, etc.)

                $PoliciesToApply = @($PolicyName)

                ForEach ( $currentpolicy in ((Get-ELBLoadBalancer -Region $region -LoadBalancerName $lb).ListenerDescriptions | Where-Object { $_.Listener.Protocol -contains 'HTTPS'}).PolicyNames )
                {
                    if ( (Get-ELBLoadBalancerPolicy -Region $region -LoadBalancerName $lb -PolicyName $currentpolicy).PolicyTypeName -ne 'SSLNegotiationPolicyType' )
                    {
                        $PoliciesToApply += @($currentpolicy)
                    }
                }

                # Configure SSL Policy
                Write-Host "`nCreating '$PolicyName' from '$ELBReferencePolicy' for $lb"
                New-ELBLoadBalancerPolicy -Region $region -LoadBalancerName $lb -PolicyName $PolicyName `
                  -PolicyTypeName SSLNegotiationPolicyType `
                  -PolicyAttribute @{ AttributeName="Reference-Security-Policy";AttributeValue="$ELBReferencePolicy"} `
                  -Force
                Write-Host "Activating policy '$PolicyName' for ELB: $lb"
                Set-ELBLoadBalancerPolicyOfListener -Region "$region" -LoadBalancerName "$lb" -LoadBalancerPort 443 -PolicyName $PoliciesToApply

                # Cleanup Old Policies
                ForEach ($policy in (Get-ELBLoadBalancerPolicy -Region "$region" -LoadBalancerName "$lb" | Where-Object {$_.PolicyTypeName -eq 'SSLNegotiationPolicyType'}).PolicyName)
                {
                    if ( $policy -ne $PolicyName -and $policy -ne $ELBReferencePolicy )
                    {
                        Write-Host "Removing old policy '$policy' from ELB: $lb"
                        Remove-ELBLoadBalancerPolicy -Region "$region" -LoadBalancerName "$lb" -PolicyName $policy -Force
                    }
                }
            }
        }
    }
    Else
    {
        Write-Host "Region $region does not contain policy $ELBReferencePolicy"
    }
}

Chef & Berkshelf – SSL Certificate Validation Error on Windows

When using Windows, Chef, Vagrant and Berkshelf, you may encounter an issue with certificate validation. The error you may encounter is as follows:

Faraday::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed

The problem is Ruby is expecting the SSL_CERT_FILE environmental variable to be set. This should point to a CA bundle to use for SSL certificate validation. If this is not set, certificate validation will fail. You can adjust the Berkshelf configuration to not verify SSL certificates, but I have found this setting to be problematic and sporadically not work. The better option, is to actually set the environmental variable.

Luckily, Vagrant comes with a CA bundle. We can leverage Vagrant’s installation information in the registry to determine the installation location of Vagrant and use this to set the SSL_CERT_FILE variable appropriately. The following PowerShell script can be used to do this. It has a set and unset function. By default, running ./Set-SSLCert_Chef_Vagrant.ps1 set will set SSL_CERT_FILE as a user-specific environmental variable.

After running this script, close and re-open any open command prompts for the new variable to take effect. Vagrant is required for this script to work correctly.

# Filename: Set-SSLCert_Chef_Vagrant.ps1
# Brian Dwyer - 5/22/14

# ***USAGE***
# To Setup the SSL_CERT_FILE Variable
# ./Set-SSLCert_Chef_Vagrant.ps1 set

# To Remove the SLL_CERT_FILE Variable
# ./Set-SSLCert_Chef_Vagrant.ps1 unset

# System-Wide Environmental Variables
$System_Vars='HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'

# User-Specific Environmental Variables
$User_Vars='HKCU:\Environment'

# Check if Vagrant is installed
If (!$env:Path.Contains('Vagrant'))
  {
    echo ""
    echo "/======| Error: Vagrant does not seem to be installed. |=====\"
    echo ""
    pause
    exit
    }

# Get Vagrant Installation Directory
$Vagrant_DIR = $env:Path.Split(';') -like "*vagrant*" | Out-String -Stream | Split-Path -Parent

# Registry Property Key/Value
$Reg_Key='SSL_CERT_FILE'
$Reg_Value="$Vagrant_DIR\embedded\cacert.pem"

# Setup
If ( $args[0] -eq 'set' )
  {
  echo "/======| Setting up Registry Key... |=====\"
  Set-ItemProperty $User_Vars -Name $Reg_Key -Value $Reg_Value
  pause
  exit
  }
Elseif ( $args[0] -eq 'unset' )
  {
  echo "/======| Removing Registry Key... |=====\"
  Remove-ItemProperty $User_Vars -Name $Reg_Key
  pause
  exit
  }
 Else
  {
  echo '------------------------------------------------'
  echo "|*|-Set SSL_CERT_FILE Environmental Variable-|*|"
  echo '------------------------------------------------'
  echo "|   Use 'set' or 'unset' to control script     |"
  echo '------------------------------------------------'
  pause
  exit
  }

How to Empty the Active Directory Recycling Bin

One of the coolest new features in Server 2008 R2 and 2012 is the ability to recover deleted Active Directory objects. Your forest functional level must be at least 2008 R2 in order to activate this feature. Server 2008 R2 introduced the AD Administrative Center which provides a nice GUI to restore deleted objects after activated.  However, eventually one might want to permanently empty the contents of this recycling bin. There is no way to do this via the GUI. The following PowerShell line will do just that.

Get-ADObject -Filter 'isDeleted -eq $true -and Name -like "*DEL:*"' -IncludeDeletedObjects | Remove-ADObject -Confirm:$false

If you want to see what it is going to remove, only use the Get-ADObject section of the command prior to the pipe. Remove -Confirm:$false if you want to see and confirm each deletion individually. Only run this command in the form seen here if you are absolutely sure you want to empty the trash!