Disabling Automatic Delivery of Internet Explorer 10

Microsoft just released the Toolkit to Disable Automatic Delivery of Internet Explorer 10

It’s like any other previous kit that they released: it contains an adm template for group policies and a good old batch file.

The batch file in that toolkit is actually able to enable or disable the delivery of IE10 on the local computer or on a remote target as long as you’ve administrative credentials, activated the remote registry and configured the firewall properly to allow the Core Networking and File and Print features.

In a powershell 3 console, to block it locally you can do:

& E:\IE10_Blocker.cmd --% . /B

You’ll end up with this in the registry

To revert back to the default behavior you can do:

& E:\IE10_Blocker.cmd --% . /U

With Powershell, we can actually easily get a function to first query the presence of the setting in the registry and get a second function to set the setting on and off on a Windows 7 computer running Powershell V2 like this:

#Requires -Version 2.0

Function Get-IE10BlockerStatus {
[CmdletBinding()]
Param()
Begin {
    $key = 'HKLM:\SOFTWARE\Microsoft\Internet Explorer\Setup\10.0'
} 
Process {
    Write-Verbose -Message "Checking for IE 10 Blocker status on $($env:COMPUTERNAME)"
    try {
        $status = Get-ItemProperty -Path $key -Name DoNotAllowIE10 -ErrorAction Stop
    } catch {
        $status = New-Object -TypeName PSobject -Property @{ DoNotAllowIE10 = 0}
    }
    switch ($status.DoNotAllowIE10) {
        0 {
            # Keeping a V2 compliant code
            New-Object -TypeName PSObject -Property @{Status = 'Allowed'}
            break
        }
        1 {
            New-Object -TypeName PSObject -Property @{Status = 'Blocked'}
            break
        }
        default {
            # Should not get there, but if it really fails, it's probably off
            New-Object -TypeName PSObject -Property @{Status = 'Unknown'}
        }
    }
}
End {}
}

Function Set-IE10BlockerStatus {
[CmdletBinding()]
Param(
    [switch]$Enable = $true
)
Begin {
    $key = 'HKLM:\SOFTWARE\Microsoft\Internet Explorer\Setup\10.0'    

    # Make sure we run as admin            
    $usercontext = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()            
    $IsAdmin = $usercontext.IsInRole(544)                                                       
    if (-not($IsAdmin)) {            
        Write-Warning "Must run powerShell as Administrator to perform these actions"            
        break
    }             
}
Process {
    switch ($Enable) {
        $false {
            Write-Verbose -Message "Setting status of IE10 on $($env:COMPUTERNAME) to: Allowed"
            try {
                # Attempt to delete the value if it exists
                Get-ItemProperty -Path $key -Name DoNotAllowIE10 -ErrorAction Stop | Remove-ItemProperty -Name DoNotAllowIE10 -ErrorAction Stop
            } catch {
                # If it failed, it means it doesn't exist
            }
            break
        }
        $true {
            Write-Verbose -Message "Setting status of IE10 on $($env:COMPUTERNAME) to: Blocked"
            try {
                # Create the key if required
                if (-not(Test-Path $key))  {
                    New-Item -Path $key -Force  -ItemType Container -ErrorAction Stop
                }
                # Set the value
                Set-ItemProperty -Path $key -Name DoNotAllowIE10 -Value 1 -Type DWORD -Force -ErrorAction Stop 
            } catch {
                Write-Warning -Message "Failed to set the value in the registry of $($env:COMPUTERNAME) because $($_.Exception.Message)"
            }
            break
        }
        default {
            Write-Warning -Message "Should never get here"
        }
    }
}
End {}

<#

.SYNOPSIS    
    Block IE10 on WU/MU
 
.DESCRIPTION  
    Prevents the machine from receiving Internet Explorer 10 via Automatic Updates on the Windows Update and Microsoft Update sites

.INPUTS
    None
        This script doesn't accepts any input.

.OUTPUTS
    None
        This scripts doesn't have any ouput.

 .LINK    
    https://p0w3rsh3ll.wordpress.com/
 
.EXAMPLE    
    Set-IE10BlockerStatus -Enable -Verbose

    This will block IE10 on the local computer

.EXAMPLE    
    Set-IE10BlockerStatus -Enable:$false -Verbose

    This will revert back to default settings and allow you to receive IE10 via WU/MU

.NOTES    
    Name: Set-IE10BlockerStatus
    Author: Emin Atac
    DateCreated: 31/01/2013

#>
}

Here’s how to use the above functions:

Get-IE10BlockerStatus -Verbose            
            
Set-IE10BlockerStatus -Enable -Verbose            
            
Get-IE10BlockerStatus -Verbose            
            
Set-IE10BlockerStatus -Enable:$false -Verbose            
            
Get-IE10BlockerStatus -Verbose            

It can easily be extended to target remote computers. It can be done via WMI or directly through the remote registry capability or by using powershell remoting. I prefer the latest as it’s as easy as copy/pasting the above functions code into a PS1 file and add one of the above sample lines (based on your needs) and run:

Invoke-Command -ComputerName RemtePC1,RemotePC2 -FilePath .\MyFile.ps1 -Credential (Get-Credential)

Have fun! 😎

Download System Center ConfigMgr 2012 SP1 prerequisites

I’ve recently seen on the web a powershell version 2 script responsible for downloading the System Center Configuration Manager (ConfigMgr) SP1.

As you may know you may need to redownload the binaries as well:

Here’s my version for powershell v3 πŸ˜€

#Requires -Version 3.0

Function Get-ConfigMgr2012SP1Prerequisites {
[CmdletBinding()]    
param(
    [parameter(Mandatory)]            
    [system.string]$TargetFolder = $null           
)
Begin {
    $HT = @{}
    $HT += @{ ErrorAction = 'Stop'}
    # Validate target folder
    try {
        Get-Item $TargetFolder @HT | Out-Null
    } catch {
        Write-Warning -Message "The target folder specified as parameter does not exist"
        break
    }
}
Process {

    # Step 1 download the manifest file
    try {
        Invoke-WebRequest -Uri "http://go.microsoft.com/fwlink/?linkid=269720" -OutFile (Join-Path -Path $TargetFolder -ChildPath SP1.Manifest.cab) @HT
        Get-Item (Join-Path -Path $TargetFolder -ChildPath SP1.Manifest.cab) @HT | Unblock-File @HT
    } catch {
        Write-Warning -Message "Failed to download the manifest file"
        return
    }

    # Create subfolders
    'SP1.Manifest','SP1.Prerequisites' | ForEach-Object -Process {
        if (-not(Test-Path (Join-Path -Path $TargetFolder -ChildPath $_))) {
            try {
                New-Item (Join-Path -Path $TargetFolder -ChildPath $_) -ItemType Directory  @HT |Out-Null
            } catch {
                Write-Warning -Message "Failed to create subfolder because $($_.Exception.Message)"
                return
            }
        }
    }

    if (Test-Path (Join-Path -Path $TargetFolder -ChildPath SP1.Manifest)) {
    
        # Step2: Expand the manifest cab
        if (Test-Path (Join-Path -Path $TargetFolder -ChildPath "SP1.Manifest\sp1.manifest.cab")) {
            try {
                Remove-Item -Path (Join-Path -Path $TargetFolder -ChildPath "SP1.Manifest\sp1.manifest.cab") -Force @HT
            } catch {
                Write-Warning -Message "Failed to remove previous extracted content because $($_.Exception.Message)"
                return
            }
        }
        & (Get-Command expand) @("$TargetFolder\SP1.Manifest.cab",'-F:*.*',"$TargetFolder\SP1.Manifest") |Out-Null

        # Step 3: Read its XML content
        $updates = ([xml](Get-Content (Join-Path -Path $TargetFolder -ChildPath "SP1.Manifest\sp1.manifest.cab"))).ConfigMgr.Group.SelectNodes("*")

        # Step 4: Download each file
        if ($updates) {
        $i = 0
        $updates | ForEach-Object -Process {
                $i++
                Write-Progress -activity "Downloading file: $($_.FriendlyName)" -status "Percent completed: " -PercentComplete (($i/$updates.Count)*100)
                try {
                    Invoke-WebRequest -Uri ([URI]$($_.Source)) -OutFile (Join-Path -Path $TargetFolder -ChildPath "SP1.Prerequisites\$($_.CopyName)") @HT
                } catch {
                    Write-Warning -Message "Failed to download content because $($_.Exception.Message)"
                    return
                }
                if ($?) {
                    Write-Verbose "Validating signature of file $($_.FriendlyName)" 
                    $DownloadedFileSHA2 = $null
                    $DownloadedFileSHA2 = Get-CheckSum -FilePath (Join-Path -Path $TargetFolder -ChildPath "SP1.Prerequisites\$($_.CopyName)") -Type SHA256
                    if ($DownloadedFileSHA2 -eq $_.SHA256) {
                        Write-Verbose "Digital signature of file $($_.FriendlyName) is valid" 
                    } else {
                        Write-Warning -Message "Digital signature of file $($_.FriendlyName) is invalid"
                    }
                }
            }
        } else {
            Write-Warning -Message "Failed to read or find the files to download in the XML file"
        }
     } else {
        Write-Warning -Message "Failed to find directory $(Join-Path -Path $TargetFolder -ChildPath SP1.Manifest)"
    }
}
End {}
}

NB: The Get-Checksum function being used in the above code is available on this page: https://p0w3rsh3ll.wordpress.com/2012/06/26/getting-file-checksum/

Get-ConfigMgr2012SP1Prerequisites -TargetFolder "E:\CM2012\Test" -Verbose

How to improve your Powershell skills

Today, I’ve seen a link on twitter to a link on the technet gallery Get AD-Computer network settings

I’ve quickly said that and @mattifestation replied:

Matt is probably right but I want to take the opportunity to help Andrea and other powershell beginners to show how to improve your skills.

First, Powershell it’s much more than a Unix shell where you cut & replace strings (grep/awk/sed/…), it’s actually full object oriented.
To demonstrate this, let’s take the following subset of the code:

$NICs = Get-WmiObject -Namespace 'root\cimv2' -Class 'Win32_NetworkAdapterConfiguration' -Filter {IPEnabled='True'}            
             
$NICs | ForEach-Object {             
    $NIC_Entry = New-Object system.object            
            
    $NIC_Entry | Add-Member -Type NoteProperty -Name 'Interface Index' -Value ($_.Index) -Force             
    $NIC_Entry | Add-Member -Type NoteProperty -Name 'IP-Address' -Value $_.IPAddress -Force             
    $NIC_Entry | Add-Member -Type NoteProperty -Name 'Subnetmask' -Value $_.IPSubnet -Force             
    $NIC_Entry | Add-Member -Type NoteProperty -Name 'MAC-Address' -Value $_.MACAddress -Force             
    $NIC_Entry | Add-Member -Type NoteProperty -Name 'IP Default Gateway' -Value $_.DefaultIPGateway -Force             
}            
return $NIC_Entry

First let’s achieve the same thing by typing less. Instead of using Add-Member, let’s use the properties argument of New-Object:

# Instead of Add-Member            
Get-WmiObject -Class 'Win32_NetworkAdapterConfiguration' -Filter {IPEnabled='True'} | ForEach-Object -Process {            
    New-Object -TypeName PSObject -Property @{            
        'Interface Index' = $_.Index;            
        'IP-Address' = $_.IPAddress;            
        'Subnetmask' = $_.IPSubnet;            
        'IP Default Gateway' = $_.DefaultIPGateway            
    }            
}

Let’s do even less and still rename all properties:

# Using select and changing properties name            
$arofproperties = @(            
 @{label='Interface Index';expression={$_.Index}},            
 @{l='IP-Address';e={$_.IPAddress}}            
)            
Get-WmiObject -Class 'Win32_NetworkAdapterConfiguration' -Filter {IPEnabled='True'} | Select -Property $arofproperties            

If we don’t mind about using default properties names, we can simply use the full power of the pipeline by selecting a subset of properties like this:

Get-WmiObject -Class 'Win32_NetworkAdapterConfiguration' -Filter {IPEnabled='True'} | Select -Property Index,IPAddress,IPSubnet,DefaultIPGateway

Now, that we achieved the same thing, let’s plug some nice error handling on top of the code.
If you do a WMI query against remote computers, you absolutely need to do the following:

  1. Make sure to add a
    -ErrorAction Stop

    at the end of the Get-WMIobject cmdlet

  2. Put the whole statement in try/catch block

Hands on! Let’s say I’ve an array of computers.

$Computers = "127.0.0.1","::1","localhost"

We can do:

$Computers | ForEach-Object -Process {            
    $Computer = $_            
    $allWMIproperties = $null            
    try {            
        $allWMIproperties = Get-WmiObject -Class 'Win32_NetworkAdapterConfiguration' -Filter {IPEnabled='True'} -ErrorAction Stop  -ComputerName $Computer            
    } catch {            
        Write-Warning -Message "WMI query again computer $Computer failed because $($_.Execption.Message)"            
    }            
    if ($allWMIproperties) {            
        # It worked, so I can continue            
    }            
}

Last thing. Instead of talking about splatting which another topic, I’d like to mention that the Active Directory module is a feature that first needs to be added to a workstation or a server like this:

Add-WindowsFeature -Name RSAT-AD-PowerShell -Restart:$false -Verbose

Even loading a module can go wrong. You can even disable the automatically mounted AD: drive and on Windows 2012, you’ve now the module preloading feature…
While not being perfect especially if you disabled the automatically mounted AD: drive, I’m usually using the following code:

# First we load the Active Directory module if required            
if ((Get-Module -Name ActiveDirectory).Name -ne "ActiveDirectory") {            
            
    Write-Verbose -Message "Attempting to load Active Directory module for Powershell"            
            
    Import-Module -Name ActiveDirectory -ErrorAction SilentlyContinue            
            
    if ( (Get-PSDrive -PSProvider ActiveDirectory -ErrorAction SilentlyContinue).Name -ne "AD") {            
        Write-Warning -Message "Failed to load the ActiveDirectory Module"                    
    }            
               
}

If, you’re still reading at this point, you’ll probably think that the above tips aren’t sufficient. You’re right, it’s not enough. You should enroll in the powershell community, read blogs, books, watch videos, ask questions on powershell.org forums…and of course pratice writing scripts. The official Scripting Games will start in April but you can still subscribe to the powershell.org newsletter and participate in the Winter Scripting Games.

Quicktip: Best Practices Analyzer (BPA) on Windows server 2012

Even if you run a Core version of Windows 2012, you can have access to the Best Practices Analyzer (aka BPA) to see if the roles you’ve added and configured follow the best practices.

As an introduction, you should have a look to the following articles:

I’ve got 2 other examples where I use a straightforward approach to show only problems you should be aware of and that you can solve:

Get-BpaModel | Where Name -eq "Hyper-V" | Invoke-BpaModel            
            
Get-BpaModel | Where Name -eq "Hyper-V" |             
Get-BpaResult |  Where Resolution -ne $null |             
Format-List -property ResultNumber,Severity,Category,Title,Problem,Resolution

Recently, I’ve also quickly plugged a WSUS for testing purposes but the text for solving the issues doesn’t really help in this case:

Get-BpaModel | ? Name -match "Update Services" | Invoke-BpaModel

Get-BpaModel | ? Name -match "Update Services" |            
Get-BpaResult | ? Resolution |             
fl -p ResultNumber,Severity,Category,Title,Problem,Resolution

There warnings are actually correct but they use to old model of Windows 2008 R2. Here’s a more human readable version:

I’ve installed the built-in Windows Internal Database (WID) feature that comes along with the built-in WSUS on Windows Server 2012.
Moving the WID database to another partition is and will be another story.

Searching VirusTotal.com with PowerShell

While reading the following article on Didier Steven’s blog, http://blog.didierstevens.com/2012/10/01/searching-for-that-adobe-cert/, I’ve noticed a screenshot where VirusTotal detection results are reported. He also mentioned that we can actually search virustotal and points to his page http://blog.didierstevens.com/2012/05/21/searching-with-virustotal/ where he says:

Did you know that you can search VirusTotal? You don’t have to submit a file, but you can search for the report of a file has been submitted before. You use a cryptographic hash (MD5, SHA1, SHA256) to identify the file.

If I go to this page https://www.virustotal.com/#search and submit for example the SHA1 of the C:\Windows\SysWOW64\ntoskrnl.exe of my Windows 7 x64 SP1 (en-US) computer

I’ve got the following results:

With the following sample code, I can get the same returned as a powershell object:

$checksum = "4988e05aaae2fa035b8199295be7d4cf5527e4cb"            
$res = (Invoke-WebRequest -Uri 'https://www.virustotal.com/search/' -Method Post -Body "query=$checksum")                        
$page = Invoke-WebRequest -Method GET -Uri $res.BaseResponse.ResponseUri.OriginalString                        
$obj = New-Object -TypeName PSObject                        
    ($page.AllElements | ? { ($_.TagName -eq 'TBODY') -and ($_.outerHTML -match "$checksum") }).OuterText -split "`n" |  % {                        
    $obj | Add-Member -MemberType NoteProperty -Name ($_ -split ":")[0] -Value (-join($_ -split ":" )[1..($_ -split ":" ).count])                        
}                        
$obj

Cool, isn’t it ? Wait, there’s more. There are more detailed tabs.

We can access these tabs this way:

$page.AllElements.FindById('antivirus-results').outerHTML            
$page.AllElements.FindById('behavioural-info').outerHTML            
$page.AllElements.FindById('additional-info').outerHTML

Here’s the function that parses 2 of the 3 tabs. Parsing behavioural-info is a hassle..

# Requires -Version 3.0


Function Test-VTresults  {
    [CmdletBinding()]
    param(
    [Parameter(Mandatory,ValueFromPipeline)]
    [string[]]$CheckSum
    )
Begin {
    $URI = 'https://www.virustotal.com/search/'
}
Process {
    if ($CheckSum) {
        $CheckSum | ForEach-Object -Process {
            Write-Verbose -Message "Dealing with $_"
            $res = (Invoke-WebRequest -Uri $URI -Method Post -Body "query=$_")            
            $page = Invoke-WebRequest -Method GET -Uri $res.BaseResponse.ResponseUri.OriginalString            

            # Main tab
            $obj = New-Object -TypeName PSObject            
            ($page.AllElements | Where-Object {
                ($_.TagName -eq 'TBODY') -and ($_.outertext -match 'Detection\sratio') }
            ).OuterText -split "`n" |  ForEach-Object -Process {
                $obj | Add-Member -MemberType NoteProperty -Name ($_ -split ":")[0] -Value (-join($_ -split ":" )[1..($_ -split ":" ).count])            
            }

            # Analysis tab
            $count = 0
            $analysisar = @()
            $AVName = $DetectionDate = $DetectionRate = $null
            ([regex]'<TD(>|(\sclass=text\-red>))(?<ID>.*)</TD>').Matches(
            $page.AllElements.FindById('antivirus-results').outerHTML) | ForEach-Object -Process { 
                $count++
                switch ((@($_.Groups))[-1]) {
                    {$_ -match '\d{8}'} { $DetectionDate = $_ ; break}
                    {$_ -match '^\-$'}{ $DetectionRate = '-' ; break }
                    {$_ -match '.*'} { 
                        if ($count -eq 1) {
                            $AVName = $_ 
                        } else {
                            $DetectionRate = $_
                        }
                        ; break            }
                }
                if ($count -eq 3) {
                    $count = 0
                    $analysisar += New-Object -TypeName PSObject -Property @{
                        Update = $DetectionDate
                        Result = $DetectionRate
                        Antivirus = $AVName
                    }
                }
            }
            # Add the analysis results array as a single property
            $obj | Add-Member -MemberType NoteProperty -Name Analysis -Value $analysisar

            if ($page.AllElements.FindById('additional-info')) {
                $locations = $string = $null
                # Identify indexes where the H5 tag is located
                $locations = ([regex]'<H5>').Matches($page.AllElements.FindById('additional-info').outerHTML).Index
                if ($locations) {
                    
                    $additionalinfoar = @()
                    # Last item and content,...</LI> is missing at the end of each line for easy parsing 😦
                    # $string = $page.AllElements.FindById('additional-info').outerHTML.Substring($locations[-1])
                    
                    for ($i = 0 ; $i -le ($locations.Count-2) ; $i++) {

                        # Define the string between two H5 tags
                        $string = $page.AllElements.FindById('additional-info').outerHTML.Substring($locations[$i],($locations[$i+1]-$locations[$i]))

                        # Split this new string
                        switch -Regex ($string) {
                            # Case 1
                            '<H5>(?<Tool>.*)</H5>(?<text>.*)</TD></TR>' { 
                                $additionalinfoar += New-Object -TypeName PSObject -Property @{
                                    Item = ([regex]'<H5>(?<Item>.*)</H5>(?<Content>.*)</TD></TR>').Matches($string).Groups[1].Value
                                    Content = (([regex]'<H5>(?<Item>.*)</H5>(?<Content>.*)</TD></TR>').Matches($string).Groups[2].Value -split '<BR>') | Out-String
                                }
                            }
                            # Case 2
                            '<H5>(?<Tool>.*)</H5><PRE\sstyle=\"MAX\-WIDTH:\s\d{3}px\">(?<text>.*)' {
                                $additionalinfoar += New-Object -TypeName PSObject -Property @{
                                    Item = ([regex]'<H5>(?<Item>.*)</H5><PRE\sstyle=\"MAX\-WIDTH:\s\d{3}px\">(?<Content>.*)').Matches($string).Groups[1].Value
                                    Content = -join (
                                    $string[
                                    ([regex]'<H5>(?<Tool>.*)</H5><PRE\sstyle=\"MAX\-WIDTH:\s\d{3}px\">').Matches($string).Length..
                                    (([regex]'</PRE></TD></TR>').Matches($string).Index-1)
                                    ])
                                }
                            }
                        }
                    }
                    $obj | Add-Member -MemberType NoteProperty -Name 'additional-info' -Value $additionalinfoar 
                }
            }
            # Output 
            $obj
        }
    }
}
End {}

<#
     
.SYNOPSIS   
    Search virustotal.com for checksum and parse the returned HTML page for relevant information.
    
.DESCRIPTION 
    Submit an array of checksum MD5, SHA1, SHA256 and get the info from the main tab, analysis results and additional info.
 
.PARAMETER Checksum
    Array of checksum strings that can either be a MD5, SHA1 or SHA256
      
.NOTES   
    Name: Test-VTresults
    Author: Emin Atac
    DateCreated: 17/01/2013
      
.LINK   
    https://p0w3rsh3ll.wordpress.com
      
.EXAMPLE
    Test-VTresults 4988e05aaae2fa035b8199295be7d4cf5527e4cb
    Search virustotal.com for the SHA1 checksum of C:\Windows\SysWOW64\ntoskrnl.exe of a Windows 7 x64 SP1 (en-us) computer
 
.EXAMPLE
    a6c80ce949469cc86f6c22355f4d3bb8773fc634 | Test-VTresults
    Search virustotal.com for the SHA1 checksum of eicar.com
 
#>
}

Now, let’s see how to use the above function πŸ™‚

I’ve seen the recent post about EICAR being generated by powershell on this page: http://www.obscuresecurity.blogspot.fr/2013/01/New-Eicar.html
(also published on http://poshcode.org/3874).
Let’s have some fun with it, here’s my one-liner with a different approach πŸ˜› :

[System.Text.Encoding]::UTF8.GetString(@(            
('8853793380376465809152928090885352408094415567674155'-split "(?<=\G.{2})",26)+            
'125'+            
('36697367658245838465786865826845657884738673828583458469838445707376693336724372421310'-split "(?<=\G.{2})",43))            
).Trim()|Out-File E:\eicar.txt -Force -Encoding ASCII

If I use the Get-Checksum I’ve already presented on this page, I can do

Get-CheckSum -FilePath E:\eicar.txt | Test-VTresults

'fe71aa78dc9288e9997482380c4f270495ca7631',            
'91e764c72619b2e469bded5c21e2a6826aadad62' | Test-VTresults -Verbose |            
Select -ExpandProperty Analysis

'91e764c72619b2e469bded5c21e2a6826aadad62' |             
Test-VTresults -Verbose |             
Select -ExpandProperty 'additional-info' |            
Select -ExpandProperty Content

Did I already say that Powershell rocks! 😎

Get the full reboot history for troubleshooting

  • The context

I’ve had some weird issues with my USB wireless audio headset causing a BSoD (Blue Screen of Death) 0x9f when I put the computer into sleep mode.
I’ve solved the issue by installing the following hotfix: A Windows 7-based computer does not enter sleep mode after you play audio files through a USB headset or a USB audio device http://support.microsoft.com/kb/2619331

BSoD 0x9f usbaudio.sys

When troubleshooting the issue, I used the Reliability control panel item to quickly summarize the issue.

control panel reliability monitor

Visually speaking it’s a very good built-in tool that relies on the RAC task.

RAC task

However it’s partially turned off by default on servers. Here’s a screenshot of Windows 2008 R2 SP1:
RAC task on 2008 R2

So, my idea was to get something between the Reliability monitor and the custom filtered view of the logs called ‘Administrative events’.

  • About the approach

This tip on Powershell Magazine presents how to quickly track the reboot history of a computer when things run correctly http://www.powershellmagazine.com/2012/10/15/pstip-get-your-reboot-history/

Of course, there will be a 6005 event logged after a BSoD and the hard reboot of the computer. But that doesn’t help much troubleshooting why the computer has been restarted.

I tried to gather more relevant information about the reboot history using the following command:

Get-EventLog -LogName System -Source 'Microsoft-Windows-Kernel-General',            
'Microsoft-Windows-Kernel-Power','Microsoft-Windows-Power-Troubleshooter',            
'Eventlog','User32' | Out-ExcelReport

NB: the Out-ExcelReport function can be found on http://powershell.com/cs/blogs/tips/archive/2012/12/20/sending-results-to-excel.aspx

Get-Eventlog reboot history

As you can see in the above image certain Message properties cannot be “interpreted” 😦

I’ve got the following message for example:

The description for Event ID ‘1’ in Source ‘Microsoft-Windows-Kernel-General’ cannot be found.
The local computer may not have the necessary registry information or message DLL files to display the message, or you may not have permission to access them.
The following information is part of the event:’2012-12-16T08:29:52.500000000Z’, ‘2012-12-15T19:39:38.905000000Z’

I’ve got a second problem, a DLL file needs probably to be registered with regsrv32. I’ve found an ugly way to workaround this issue. Anyway, I’ll prefer to present below a solution that work when you don’t have the above issue.

  • My solution version 1.0

Function Out-ExcelReport {
param(
        $Path = "$env:temp\$(Get-Random).csv"
)
    $Input | Export-Csv -Path $Path -Encoding UTF8 -NoTypeInformation -UseCulture
    Invoke-Item -Path $Path
}


Function Get-EventsHelper {
[CmdletBinding()]
Param(
 $HashTable,
 [string]$Provider,
 [string]$LogName,
 [int32[]]$EventID
)
Begin{
}
Process {
    $SuperHT = @{}
    $SuperHT += @{LogName = $LogName}
    if ($EventID) { $SuperHT += @{ID = $EventID} }
    if ($Provider) { $SuperHT += @{ProviderName = $Provider} }
    try {
        Get-WinEvent -FilterHashtable $SuperHT @HashTable
    } catch {
        Write-Warning -Message "Failed to get events from computer $Computer because $($_.Exception.Message)"
        if ( ($_.Exception.Message -eq "The RPC server is unavailable") -or ($_.CategoryInfo.Reason -eq 'UnauthorizedAccessException')) { 
            return
        }
    }
}
End {}
}

Function Get-RebootHistory {
 Param(            
    [CmdletBinding()]            
        [parameter(ValueFromPipeline = $True,ValueFromPipeLineByPropertyName = $True)]            
        [Alias('CN','__Server','IPAddress','Server')]            
        [string[]]$Computername = $Env:Computername,            
                    
        [parameter()]            
        [Alias('RunAs')]            
        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty                   
    )            
Begin            
{            
    # Make sure we run as admin            
    $usercontext = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()            
    $IsAdmin = $usercontext.IsInRole(544)                                                       
    if (-not($IsAdmin))            
    {            
        Write-Warning "Must run powerShell as Administrator to perform these actions"            
        return            
    }             
    $results = @()
}             
Process             
{            
    $ComputerName | ForEach-Object -Process {            
        $allevents = @()
        $Computer = $_            
        # Prepare HT            
        $HT = @{}
        # Supplied Alternate Credentials?                        
        If ($PSBoundParameters['Credential'])  {                        
            $HT.credential = $Credential                        
        }            
        If ($Computer -eq $Env:Computername) {            
            $HT.remove('Credential')            
        } Else {            
            $HT += @{Computername = $Computer}            
        }     
        Write-Verbose -Message "Targeting computer $Computer"       
        $HT += @{ ErrorAction = 'Stop'}

        $allevents += Get-EventsHelper -Hashtable $HT -LogName "System" -Provider "User32"
        $allevents += Get-EventsHelper -Hashtable $HT -LogName "System" -Provider "Microsoft-Windows-WER-SystemErrorReporting" -EventID 1001,1018
        $allevents += Get-EventsHelper -Hashtable $HT -LogName "System" -Provider "Microsoft-Windows-Kernel-General" -EventID 1,12,13
        $allevents += Get-EventsHelper -Hashtable $HT -LogName "System" -Provider "Microsoft-Windows-Kernel-Power" -EventID 42,41,109
        $allevents += Get-EventsHelper -Hashtable $HT -LogName "System" -Provider "Microsoft-Windows-Power-Troubleshooter" -EventID 1
        $allevents += Get-EventsHelper -Hashtable $HT -LogName "System" -Provider "Eventlog" -EventID 6005,6006,6008,6013
        $results += ($allevents | ForEach-Object -Process {
                $_ | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Computer
                $_
        } |  Sort-Object -Descending -Property RecordId)
    } # end of Foreach-Object process
    $results            
}
End {}
} # end of function

Here is how to use the above code 😎

# Store credentials being used for remote computers            
$cred = Get-Credential            
            
# Get the reboot history and store in the $r variable            
$r =  Get-RebootHistory -Computername 'localhost',"PC2",$env:computername,"PC1","V-PC739" -Verbose -Credential $cred            
            
# Display on console            
$r | Select -Property  ComputerName,TimeCreated,Id,RecordId,Message | ft -AutoSize -Property ComputerName,TimeCreated,Id,Message            
            
# Read the results in Excel            
$r | Select -Property  ComputerName,TimeCreated,Id,RecordId,Message | Out-ExcelReport

Here you can see the result in Excel: Reboot history in Excel

  • Improvements for version 2.0
  • Bonus: other useful links

Searching Microsoft security bulletins

I remember that we could tick an option to show only security bulletins that were not superseded by a new one on this page: http://technet.microsoft.com/en-us/security/bulletin/default.aspx

Now, there’s an option to ‘Show most recent updates only’, whatever that means. Wait, now Microsoft provides also an Excel file that contains all the information.

We can download, save as CSV file and import it in PowerShell as an object πŸ™‚

            
#Requires -Version 3.0            
            
$xlsxfile = [system.IO.Path]::GetTempFileName()            
try {            
    Invoke-WebRequest -Uri 'http://go.microsoft.com/fwlink/?LinkID=245778' -ErrorAction Stop  -OutFile $xlsxfile            
} catch {            
    Write-Warning -Message "Failed to download file because $($_.Exception.Message)"            
    break            
}            
            
            
if ($xlsxfile -and (Get-Item $xlsxfile).Length -gt 0) {            
                
    $ExcelObj = New-Object -ComObject "Excel.Application"            
            
    # Open the downloaded xlsx file            
    $WBook = $ExcelObj.Workbooks.Open($xlsxfile,3)            
            
    # Load type            
    # $xlFileFormat = "Microsoft.Office.Interop.Excel.XlFileFormat" -as [type]             
    # $xlFileFormat::xlCSV -eq 6            
            
    # Create derived csv file name            
    $csvfile = Join-Path -Path (Split-Path (Get-Item -Path $xlsxfile)) -ChildPath ((Get-Item -Path $xlsxfile).BaseName +  ".csv")            
            
    # Save as CSV # http://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.xlfileformat%28v=office.14%29.aspx            
    $WBook.SaveAs($csvfile,6)            
            
    # Close the workbook            
    $WBook.Close($false)            
            
    # Close Excel            
    $ExcelObj.Quit()            
                 
    # Force any remaining Excel process related to our COM object to stop            
     Get-WmiObject -Class Win32_Process | Where-Object {             
     $_.Commandline -match 'EXCEL.EXE" /automation -Embedding' } |             
     Select-Object -Property CommandLine,ProcessId | ForEach-Object -Process {             
         try {             
            Stop-Process -Id  $_.ProcessId -ErrorAction Stop            
         } catch {            
            Write-Warning -Message "Failed to stop Excel because $($_.Exception.Message)"            
         }            
     }            
}            
            
            
# There are duplicate words in the header, so we cannot use it as header as Import-CSV returns an error            
$header = 'Date Posted','Bulletin ID','Bulletin KB','Severity','Impact',            
'Title','Affected Product','Component KB','Affected Component','Impact2','Severity2','Supersedes','Reboot','CVEs'            
            
# By default, Get-content sends 1 line by 1 line through the pipeline # -ReadCount 1            
$content =  Get-Content  -Path $csvfile            
            
# The first two columns header name contains a carriage return in their cell name            
            
# To count the total number of lines we just do            
$totallines = ($content | Measure-Object).Count            
            
# Create a new file name            
$csvfile2 = Join-Path -Path (Split-Path (Get-Item -Path $csvfile)) -ChildPath ((Get-Item -Path $csvfile).BaseName +  "2.csv")            
            
# Output in this new CSV file all the lines except the first 3 lines that represent the header            
($content)[3..$totallines] | Out-File -FilePath $csvfile2            
            
# Get the text separator found in the file            
$separator = ( $content[1] -split '"')[1]            
            
# Ready to import            
$allBulletins = Import-Csv -Path $csvfile2 -Delimiter $separator -Header $header  # | Out-GridView             

Now, we’ve got every security bulletins Microsoft ever published stored as an array of objects in PowerShell

To demonstrate what can be done with these objects, I’ve got 2 examples:

First let’s count how many unique CVEs ever affected Windows 7 x64 SP1

$allW7SP1 = ($allBulletins | Where-Object {            
    $_.'Affected Product' -match "Windows 7 for x64-based Systems Service Pack 1"            
} )             
            
$allW7SP1CVEs = $allW7SP1  | ForEach-Object -Process {            
    if ($_.CVEs -match ',') {            
        $_.CVEs -split "," | ForEach-Object -Process {            
            $_.ToString()            
        }            
    } else {            
        $_.CVEs            
    }            
}             
$allW7SP1CVEs | Group-Object -NoElement | Sort-Object -Descending -Property Count            
            
'Total of unique CVEs: {0}' -f ($allW7SP1CVEs | Sort-Object -Unique).Count

My second example is about how to the list of security bulletins by KB number that apply to Windows 7 x64 SP1 and deal with the supersedence info available as a property of each object.

To be able to filter bulletins, we first need to create an array of unique KB numbers that were superseded, those that we’ll later on exclude from all the results. In other words, I’ll display only KB that do not match these superseded KB numbers.

# Select supersedence info and create an array of unique KB
$SupersededKBs  = @()
($allBulletins | ? {$_.'Affected Product' -match "Windows 7 for x64-based Systems Service Pack 1"} ) |  
Where-Object Supersedes | ForEach-Object -Process {
    if ($_.Supersedes -match ",") {
        $_.Supersedes -split "," | ForEach-Object -Process {
            $_.ToString() | % { ([regex]'(?<BulletinID>MS\d{2}\-\d{3})\[(?<KB>\d{6,8})\]').Matches($_).Groups[-1].Value}
        }
    } else {
        $_.Supersedes | % { ([regex]'(?<BulletinID>MS\d{2}\-\d{3})\[(?<KB>\d{6,8})\]').Matches($_).Groups[-1].Value}
    }
} | Sort-object -Unique | ForEach-Object -Process  {
    $SupersededKBs += $_
}

# Get bulletins that were not superseded
($allBulletins | ? {$_.'Affected Product' -match "Windows 7 for x64-based Systems Service Pack 1"} ) | % {
    if ($_.'Component KB' -notin $SupersededKBs) {
        $_
    }

} | Select-Object -Property @{n='PublishDate';e={Get-Date -Date $_.'Date Posted' -Format 'yyyy-MM-dd' }},
'Bulletin ID','Component KB','Affected Component',CVEs | Sort-Object -Property "PublishDate" -Descending |
Format-Table -AutoSize

There’s actually a third way to query MS security bulletins: by using the webservice associated the page http://technet.microsoft.com/en-us/security/bulletin/default.aspx

$MSBulletins = New-WebServiceProxy -Uri 'http://technet.microsoft.com/sto/services/BulletinSearch.asmx'

To discover methods associated with the web service, we can do:

$MSBulletins | Get-Member -MemberType Methods            
            
$MSBulletins.GetBulletins

Although, there’s a webpage to test the web service on: http://technet.microsoft.com/sto/services/BulletinSearch.asmx?op=GetBulletins
and there’s a XML description of it on : http://technet.microsoft.com/sto/services/BulletinSearch.asmx?WSDL, I cannot figure out what string should be passed to the ‘locale’ parameter.
I can see a web request with ‘en-us’ as locale (see image below), but it doesn’t work with the powershell web service object and when invoking its GetBulletins method.

We can list all products and their associated ID like this:

$MSBulletins.GetProducts(1)

Even making it fail, doesn’t help (me) much:

Any help would be appreciated.