Device drivers’ events on Windows 8

As you can see in the following screenshots, there’s a new tab in the device manager that shows events related to this device.
new device manager events tab
If you click the “View All Events”, the custom view is created on the fly in the event viewer.
eventlog custom view
With powershell 3 we can achieve the same view and even better we can gather these filtered events as objects.
We can easily add query against remote computers but we can not specify credentials as the Get-EventLog hasn’t currently a credentials parameter.

# Requires -version 3.0

Function Get-DeviceEvents
{
    [CmdletBinding()]
    param(

        [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]            
        [Alias("CN","__SERVER","IPAddress")]            
        [system.string[]]$ComputerName=$Env:Computername,

        [parameter()]            
        [system.string[]]$HardwareID = $null
    )
    Begin {
        $paramHT = @{
            ErrorAction = 'Stop'
        }
    }
    Process {
        $Computername | ForEach-Object -Process {
            $Computer = $_
            If ($Computer -ne $Env:Computername)            
            {            
                $paramHT += @{Computername = $Computer}            
            }         

            $HardwareID | ForEach-Object -Process {
                $devid = $_
                # Replace ampersand
                $hwid = $devid -replace "&","&"

                # Create the XML query
                $query = @"
                <QueryList>
                  <Query Id="0" Path="Microsoft-Windows-Kernel-PnP/Configuration">
                    <Select Path="Microsoft-Windows-Kernel-PnP/Configuration">
                    *[System/Provider[@Name='Microsoft-Windows-Kernel-PnP']
                     and EventData/Data
                     [@Name='DeviceInstanceID']='$hwid']
                     </Select>
                    <Select Path="System">
                    *[System/Provider[@Name='Microsoft-Windows-UserPnp']
                    and UserData/*/DeviceInstanceID='$hwid']
                    </Select>
                  </Query>
                </QueryList>
"@
                try
                {
                    Get-WinEvent -FilterXml $query @paramHT | ForEach-Object -Process {
                        Get-EventLog -InstanceId $($_.Id) -LogName $($_.LogName) -AsBaseObject -Index ($_.RecordID) @paramHT
                    }
                } catch {
                    Write-Warning -Message "Failed to query events against computer $Computer for DeviceID $devid $($_.Exception.Message)"
                }
            }
        }
    } 
    End {}
} # end of function


Advertisements

Enable remote desktop on Windows 2012

Quick function to enable remote desktop on a Windows 2012 server even if Remoting is already enabled by default…
I haven’t provided any help in the function as its content is more that explicit (at least for me 😛 )

Function Enable-RemoteDesktop             
{            
[CmdletBinding()]            
param()            
    Begin            
    {            
        # Make sure we run on a Windows 2012 server            
        if (-not($PSVersionTable.BuildVersion -gt ([version]'6.2')))            
        {            
            Write-Warning -Message "This function can only run on Windows 8 or 2012"            
            return            
        }            
        # Make sure we run as admin            
        $usercontext = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()            
        $IsAdmin = $usercontext.IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")                               
        if (-not($IsAdmin))            
        {            
            Write-Warning "Must run powerShell as Administrator to perform these actions"            
            return            
        }             
    }             
    Process             
    {            
        # Set the registry value            
        try            
        {            
            Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 0 -Force -ErrorAction Stop            
            if ($?)            
            {            
                Write-Verbose -Message "Remote desktop registry value set"            
            }            
        } catch {            
            Write-Warning -Message "Failed to set the registry value that turns on remote desktop"            
        }            
        # Set the firewall rule            
        try            
        {            
            Set-NetFirewallRule -DisplayGroup "Remote Desktop" -Enabled:True -ErrorAction Stop            
            if ($?)            
            {            
                Write-Verbose -Message "Remote desktop firewall rule set"            
            }            
        } catch {            
            Write-Warning -Message "Failed to enable the remote desktop group rules"            
        }            
    }            
}

Getting file checksum

The scripting guy has recently posted two blog posts on:

As you can see, I’ve a file less than 200MB from HP (it’s the support pack file):

Get-Item D:\Downloads\psp-9.00.w2k8R2.x64.exe | ft Name,@{l="Size (MB)";e={('{0:N2}' -f ($_.Length/1MB))}} -AutoSize

File size

As there isn’t any built-in function in Windows 2012, I prefered writing a small function to quickly get any hash from a file instead of loading a whole module…

But, I encountered a little problem as I was using native cmdlets to retrieve the raw file:

Get-Content -ReadCount 0 -Path $File  -Encoding byte -ErrorAction Stop

Here’s the memory consumption before running the bad function:
Bad checksum function 01
It almost doubled the memory usage before I got the ‘failed to read file’ error 😦
Bad checksum function 02
Worse, the memory hasn’t be freed up afterward. As you can see I used the following .Net command to return back to normal:

[gc]::collect()

Bad Checksum function 03

So, I’ve corrected my function to use native .Net command instead of native cmdlets.
I used the following instead of the Get-Content cmdlet

[System.IO.File]::ReadAllBytes($File)

I also put a [gc]::collect() in the end block. If there are many files to be checked, it might be better to put it in the process block so that it can free memory after each item handled through the pipeline.

Function Get-CheckSum            
{            
[CmdletBinding()]            
param(            
    [parameter(ValueFromPipeline=$true,Mandatory=$true,Position=0)]            
    [system.string[]]$FilePath = $null,            
            
    [parameter(ValueFromPipeline=$false, Mandatory=$false, Position=1)]            
    [ValidateSet("SHA1","MD5", "SHA256", "SHA384","SHA512")]            
    [system.string[]] ${Type} = "SHA1"            
            
)            
    Begin             
    {            
        Switch ($Type)            
        {            
            SHA1   { $cryptoServiceProvider = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider   }            
            MD5    { $cryptoServiceProvider = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider    }            
            SHA256 { $cryptoServiceProvider = New-Object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider }            
            SHA384 { $cryptoServiceProvider = New-Object -TypeName System.Security.Cryptography.SHA384CryptoServiceProvider }            
            SHA512 { $cryptoServiceProvider = New-Object -TypeName System.Security.Cryptography.SHA512CryptoServiceProvider }            
        } # end of switch            
    }            
    Process            
    {            
            
        $FilePath | ForEach-Object -Process {            
            $hash = $null            
            $File = $_            
            if (Test-Path $File)            
            {            
                if ((Get-Item -Path $File) -is [System.IO.FileInfo])            
                {            
                    try            
                    {            
                        $hash = $cryptoServiceProvider.ComputeHash(([System.IO.File]::ReadAllBytes($File)))            
                    } catch {            
                        Write-Warning -Message "Failed to read file $File"            
                    }            
                    if ($hash)            
                    {            
                        $result = New-Object System.Text.StringBuilder            
                        foreach ($byte in $hash)            
                        {            
                            [void]  $result.Append($byte.ToString("x2"))            
                        }            
                         ([system.string]$result)            
                    }            
            
                }            
            }            
        }            
    }            
    End             
    {            
        [gc]::collect()            
    }            
}

The above function works now perfectly on the 200 MB file whatever the algorithm requested
Checksum function 01

But it fails on big files :(. At least this time it fails very quickly and without doubling the memory 🙂
Checksum function 02

The above experience raises the following questions:

  • Why was the Get-Content cmdlet failing to read a 200 MB file ?
  • Is there any memory protection mecanism inside Windows 2012 or in powershell V3 that made if failed automatically after the memory usage doubled ?
  • Should we prefer native cmdlet or use .Net ?
  • What are the limits that the cmdlet can handle ? Idem for .Net ?

Get-Winevent cmdlet caveats

I’ve had a KMS host server broken last week as it’s underlying hardware has been changed. I’ve quickly fixed it but I’ve encountered weird things when I wanted to filter eventlogs on client machines (Windows 7 with Office 2010).

First I’ve looked at Office 2010, I fired up the eventvwr to discover the provider name and did the following that worked perfectly fine:
getwinevent 01

Get-WinEvent -FilterHashtable @{logname="Application"; providername="Office Software Protection Platform Service"}

Then I wanted to have a look to Windows 7 activation, I did the same thing, I fired up the eventvwr to figure out the provider name:
getwinevent 02

Get-WinEvent -FilterHashtable @{logname="Application"; providername="Security-SPP"}

Note that I’m running on powershell V2 and I’ve got not 1 error but 3 errors 🙂
getwinevent 03

At this step, I couldn’t figure out which error was relevant and coherent. So I used Get-help and noticed that you can list provider names pretty easily, so I did:

(get-winevent -listlog Application).providernames | Select-String -Pattern "SPP"

I’ve got two results that proved that I shouldn’t have copied “Security SPP” I’ve found in the eventvwr.
Then I double checked with the following command:

Get-WinEvent -ListProvider * | Where { $_.Name -match "SPP" }

And it got better as I could at least differentiate between errors:
winevent 04

I flipped then back to the eventvwr on the XML view to get more details about the provider names.
getwinevent 05
getwinevent 06

To really make sure I was getting it right, I tested the following (see inline comments)

            
# Using a simple provider name such as the Office one works well            
Get-WinEvent -ProviderName "Office Software Protection Platform Service" -MaxEvents 5            
            
# However a simple command with a complex provider name doesn't work and worse returns an error            
Get-WinEvent -ProviderName "SPP"            
            
# Security-SPP is definitively  the wrong provider name. Now, I got only 1 error            
Get-WinEvent -ProviderName "Security-SPP"            
            
# No error returned but no events as well, strange...            
Get-WinEvent -ProviderName Microsoft-Windows-Security-SPP            
            
# Whereas the following returns events             
(get-winevent -listprovider Microsoft-Windows-Security-SPP).events

getwinevent 07

The example 14 in the help of the get-winevent cmdlet shows the various ways to filter events.
Filtering on the right is not an option as it’s performs too slowly. Now, we know that FilterHashtable is not an option for complex provider names.
So let’s use the XML way…


$xml = @"
<QueryList>
    <Query Id="0" Path="Application">
        <Select Path="Application">
            *[System[Provider[@Name ='Microsoft-Windows-Security-SPP']]]
        </Select>
    </Query>
</QueryList>
"@

Get-WinEvent -FilterXml $xml -MaxEvents 5

… which proves how to handle such caveats…to finally get it right… 😉

Changing CDrom drive letter on Windows 2012

One of my colleague asked the other day how he could change the CDrom drive letter on Windows 2012 core edition.
I noticed recently that we were not the only ones complaining about that, see http://mssecbyben.wordpress.com/2012/06/14/howto-install-a-new-domain-on-server-2012-core/

Of course, old ways of doing it are still working. I mean that you can do it using diskpart.exe (the command line disk management utility) or using mountvol.exe.
But these two utility have some limitations in automation especially when you’ve more than one CD drive on the computer.

As we were on Windows 2012, I immediately started to look for the new promising powershell cmdlets that aim to replace the diskpart utility. I was a little bit disappointed when I saw that you actually couldn’t change easily the CDrom drive letter 😦

So I start using WMI as I was 100% sure that we could differentiate volume types. All I needed was to find the appropriate method to change the letter. WMI was the best approach as it could also easily target remote computers.

Then I realized that I needed to find out what letters were available. I recalled the nice brainteaser Shay Levy proposed on January where we should find unsused drive letters. http://www.powershellmagazine.com/2012/01/12/find-an-unused-drive-letter As bright as the 2 winning solutions are, they both have a huge drawback as they run only locally and are not using WMI. Fortunately, @Makovec proposed a WMI based solution that perfectly meets my requirements.

Here is the new function for this purpose:

Function Set-CDRomLetter             
{            
    [CmdletBinding()]            
    param(            
     [Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]            
     [Alias("CN","__SERVER","IPAddress")]            
     [string[]]$ComputerName=$Env:Computername,            
            
        [parameter()]            
        [Alias('RunAs')]            
        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty,            
            
        [Parameter(ParameterSetName='Any', Mandatory=$false, Position=1)]            
        [System.Management.Automation.SwitchParameter]${NextAvailableLetter},            
            
        [Parameter(ParameterSetName='This', Mandatory=$false, Position=1)]            
        [ValidatePattern('^[A-Za-z]$')]            
        [System.String]${SetDriveLetter}            
    )            
            
    Begin            
    {            
        $usercontext = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()            
        $IsAdmin = $usercontext.IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")                               
            
        if (-not($IsAdmin))            
        {            
            Write-Warning "Must run powerShell as Administrator to perform these actions"            
            return            
        }             
            
        # Prepare HT            
        $wmiHT = @{            
            Class = "Win32_Volume"            
            ErrorAction = "Stop"            
            Filter = 'DriveType = 5'            
        }      


#Supplied Alternate Credentials?            
        If ($PSBoundParameters['Credential']) {            
            $wmiHT.credential = $Credential            
        }

}
Process
{
$ComputerName | ForEach-Object -Process {
$Computer = $_
If ($Computer -eq $Env:Computername)
{
$wmiHT.remove('Credential')
} Else {
$wmiHT += @{Computername = $Computer}
}

try
{
$CDRomAr = Get-WmiObject @wmiHT
} catch {
Write-Warning -Message "Failed to query Win32_Volume on computer $Computer"

}
if ($CDRomAr)
{
$CDRomAr | ForEach-Object -Process {
$CDobj = $_
$wmiHT.remove('Filter')
$wmiHT.remove('Class')
$wmiHT += @{ Class = "win32_LogicalDisk" }
try
{
$AllLetters = Get-WmiObject @wmiHT
} catch {
Write-Warning -Message "Failed to query all volumes letters on computer $Computer"
return
}
if ($AllLetters)
{
$NextLetters = ([char[]](68..90)| Where-Object -FilterScript {
($AllLetters | ForEach-Object -Process {
($_.deviceid)[0]
}) -notcontains $_
})
if ($NextLetters)
{
Write-Verbose -Message "Next letter available being calculated is $($NextLetters[0])"
# Set-it
switch ($PsCmdlet.ParameterSetName)
{
Any {
# Pick-up the next one
try
{
Set-WmiInstance -InputObject $CDobj -Arguments @{DriveLetter = "$($NextLetters[0]):" } | Out-Null
Write-Verbose -Message "CDRom letter on computer $Computer set to next available letter $($NextLetters[0]):" -Verbose
} catch {
Write-Warning -Message "Failed to set the CDrom letter on computer $Computer"
}
}
This {
# Ensure that the drive letter chosen is available
if ((-join $NextLetters) -match $SetDriveLetter)
{
try
{
Set-WmiInstance -InputObject $CDobj -Arguments @{DriveLetter = "$($SetDriveLetter):" } | Out-Null
Write-Verbose -Message "CDRom letter on computer $Computer set to $($SetDriveLetter):" -Verbose
} catch {
Write-Warning -Message "Failed to set the CDrom letter on computer $Computer"
}
} else {
Write-Warning -Message "Chosen drive letter isn't available on computer $Computer"
}
}
} # end of switch

} else {
Write-Warning -Message "Failed to get the next available letter"
}
}
}
} else {
Write-Verbose -Message "CDRom not present on computer $Computer" -Verbose
}
}
}
End {}

} # end of function

IE ESC on Widows 2012

You know that you should avoid browsing the Internet on a server.
There was an easy way to access and configure the ‘Internet Explorer Enhanced Security Configuration‘ (IE ESC) under Windows 2008 servers using the ‘server manager’
IE ESC 2008

As you may know, the server manager in Windows 2012 is completly different. Unfortunately, I haven’t been able to find the same button (I didn’t search too much).

My solution is actually a small powershell function that will display the UI window only if you’ve administrative privileges.
There’s no need to check for administrative credentials as the window won’t pop out if they’re missing.

function Invoke-IEESC { & (Get-command rundll32) @("C:\Windows\system32\iesetup.dll,IEShowHardeningDialog")}

IE ESC