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.

Get Windows Update client configuration

One of the first things I do when provsioning a new server is:

  • configure the network card, assign an IP address
  • define a proxy
  • configure Windows Update settings
  • Download and install all security udpates

Every IT-pro has his own list. The Microsoft PowerShell MVP Jeffery Hicks says for example on this page: Windows Server 2012: First Five Fixes

I assume you will do the following tasks by default when setting up a new Windows Server 2012 system:
* Configure computer name
* Configure networking
* Install features and roles
* Run Windows Update
I‚Äôm not going to cover those as I think they are pretty self-evident in the Server Manager GUI, but I do have 5 additional ‚Äúfixes‚ÄĚ that I‚Äôve been using.

He’s right for a GUI environment but it isn’t very straight forward for the Core Edition or when you’ve to automate it.

I’ve already presented how to get and set a proxy server using powershell on this page: https://p0w3rsh3ll.wordpress.com/2012/10/07/getsetclear-proxy/

Recently the Microsoft MVP Jan Egil Ring presented on the Hey Scripting guy’s blog how to Use PowerShell to Configure the NIC on Windows Server 2012

For listing and configuring Windows updates settings, currently I only know the excellent work done by Boe Prox for corporate environments. Here are some links to the great resources Boe shared and still maintains:

But this isn’t exactly what I was looking for, espcially for Non‚ÄďActive Directory environments. I also wanted a console output where I don’t need to know that AUoptions represents the behavior of Automatic udpates notifications and that a value of 4 means that it should “install updates automatically”.

I didn’t want a script for Managing Windows Update with PowerShell that doesn’t take into account group policy (GPO) settings.

Even the module written by Michal Gajda and presented by Ed Wilson in the following Hey scripting guy’s blog post didn’t propose a solution to read the Windows update settings.

So I’ve been using the following resources to write my own function.

I also wanted the function to work on a freshly installed computer where Windows Updates settings haven’t been configured yet.

Function Get-WUSettings {            
[cmdletbinding()]            
Param(            
[switch]$viaRegistry=$false            
)            
Begin {            
    # Get the Operating system            
    $OSVersion = [environment]::OSVersion.Version            
            
    # Initialize object            
    $WshShell = New-Object -ComObject Wscript.Shell            
            
    $polkey = 'HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU'            
    $stdkey = 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update'            
}            
Process {            
    if ($viaRegistry) {            
        try {            
            $AUEnabled = $WshShell.RegRead("$polkey\NoAutoUpdate")            
        } catch {            
            # if this value is absent, it means it's turned on            
            $AUEnabled = 0            
        }            
        Switch ($AUEnabled) {            
            1 {$AUEnabled = $false}            
            0 {$AUEnabled = $true }            
        }            
        try {            
            $AUOptions = $WshShell.RegRead("$polkey\AUOptions")            
        } catch {            
            try {            
                $AUOptions = $WshShell.RegRead("$stdkey\AUOptions")            
            } catch {            
                $AUOptions = 0            
            }            
        }            
        Switch ($AUOptions) {            
            0 {$AUNotificationLevel = 'Not Configured'}            
            1 {$AUNotificationLevel = 'Never check for updates'}            
            2 {$AUNotificationLevel = 'Notify Before Download'}            
            3 {$AUNotificationLevel = 'Notify Before Installation'}            
            4 {$AUNotificationLevel = 'Install updates automatically'}            
        }            
        try {            
            $IncludeRecommendedUpdates = $WshShell.RegRead("$polkey\IncludeRecommendedUpdates")            
        } catch {            
            # if the value is absent we get it from            
            $IncludeRecommendedUpdates = $WshShell.RegRead("$stdkey\IncludeRecommendedUpdates")            
        }            
        Switch ($IncludeRecommendedUpdates) {            
            0 {$GetRecommendedUpdates = $false}            
            1 {$GetRecommendedUpdates = $true}            
        }            
       try {            
            $UseWUServerVal = $WshShell.RegRead("$polkey\UseWUServer")            
        } catch {            
            # if the value doesn't exist, it means that we don't use a WSUS server            
            $UseWUServerVal = 0            
        }            
        Switch ($UseWUServerVal) {            
            1 {$UseWUServer = $true}            
            0 {$UseWUServer = $false }            
        }            
        # Create a default object with a subset of properties            
        $obj = New-Object -TypeName psobject -Property @{            
            'Is Automatic Update Enabled' = $AUEnabled            
            'Use a WSUS Server' = $UseWUServer            
            'Automatic Updates Notification' = $AUNotificationLevel;            
            'Receive recommended udpates' = $GetRecommendedUpdates;            
        }            
        if ($OSVersion -lt [version]'6.2') {            
            try {            
                $ScheduledInstallDay  = $WshShell.RegRead("$polkey\ScheduledInstallDay")            
                $ScheduledInstallTime = $WshShell.RegRead("$polkey\ScheduledInstallTime")            
            } catch {            
                try {            
                    $ScheduledInstallDay  = $WshShell.RegRead("$stdkey\ScheduledInstallDay")            
                    $ScheduledInstallTime = $WshShell.RegRead("$stdkey\ScheduledInstallTime")            
                } catch {            
                    # Absent = Every Day @3 AM but I prefer to leave it blank in the returned object            
                }            
            }            
            Switch ($ScheduledInstallDay) {            
                0 {$InstallDay = 'Every Day'}            
                1 {$InstallDay = 'Every Sunday'}            
                2 {$InstallDay = 'Every Monday'}            
                3 {$InstallDay = 'Every Tuesday'}            
                4 {$InstallDay = 'Every Wednesday'}            
                5 {$InstallDay = 'Every Thursday'}            
                6 {$InstallDay = 'Every Friday'}            
                7 {$InstallDay = 'Every Saturday'}            
            }            
            if ($ScheduledInstallTime) {            
                $InstallTime = New-TimeSpan -Hours $ScheduledInstallTime            
            }            
            $obj | Add-Member -MemberType NoteProperty -Name 'Install Frequency' -Value $InstallDay            
            $obj | Add-Member -MemberType NoteProperty -Name 'Install Time' -Value $InstallTime            
        } else {            
            # These properties don't exist anymore on Windows 8            
        }            
        # Add extra properties            
        if ($UseWUServer) {            
            try {            
                $WUServer = $WshShell.RegRead('HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\WUServer')            
                $WUStatusServer =  $WshShell.RegRead('HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\WUStatusServer')            
            } catch {            
                # we silently fail            
            }            
            $obj | Add-Member -MemberType NoteProperty -Name 'WSUS Server' -Value $WUServer            
            $obj | Add-Member -MemberType NoteProperty -Name 'WSUS Status URL' -Value $WUStatusServer            
        }            
        try {            
            $OptinGUID = $WshShell.RegRead('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\DefaultService')            
        } catch {            
            # Fail silently            
        }            
        if ($OptinGUID -eq '7971f918-a847-4430-9279-4a52d1efe18d') {            
            $obj | Add-Member -MemberType NoteProperty -Name "Opted-in Microsoft Update" -Value $true            
        } else {            
            $obj | Add-Member -MemberType NoteProperty -Name "Opted-in Microsoft Update" -Value $false            
        }            
        # Return our object            
        $obj            
            
    } else {            
        # We use Com Object            
        $COMWUSettings = (New-Object -ComObject Microsoft.Update.AutoUpdate).Settings            
        # Settings might be controlled by GPO            
        if ($COMWUSettings.ReadOnly) {            
            # Use the registry            
            Get-WUSettings -viaRegistry:$true            
            break            
        } else {            
            $UseWUServer = $false            
        }            
        Switch ($COMWUSettings.NotificationLevel) {            
            0 {$AUNotificationLevel = 'Not Configured'}            
            1 {$AUNotificationLevel = 'Never check for updates'}            
            2 {$AUNotificationLevel = 'Notify Before Download'}            
            3 {$AUNotificationLevel = 'Notify Before Installation'}            
            4 {$AUNotificationLevel = 'Install updates automatically'}            
        }            
        $isAUenabled = (New-Object -ComObject Microsoft.Update.AutoUpdate).serviceEnabled            
        $obj = New-Object -TypeName psobject -Property @{            
            'Is Automatic Update Enabled' = $isAUenabled            
            'Automatic Updates Notification' = $AUNotificationLevel;            
            'Use a WSUS Server' = $UseWUServer            
            'Receive recommended udpates' = $COMWUSettings.IncludeRecommendedUpdates;            
        }            
        if ($OSVersion -lt [version]'6.2') {            
            Switch ($COMWUSettings.ScheduledInstallationDay) {            
                0 {$InstallDay = 'Every Day'}            
                1 {$InstallDay = 'Every Sunday'}            
                2 {$InstallDay = 'Every Monday'}            
                3 {$InstallDay = 'Every Tuesday'}            
                4 {$InstallDay = 'Every Wednesday'}            
                5 {$InstallDay = 'Every Thursday'}            
                6 {$InstallDay = 'Every Friday'}            
                7 {$InstallDay = 'Every Saturday'}            
            }            
            if ($COMWUSettings.ScheduledInstallationTime) {            
                $InstallTime = New-TimeSpan -Hours $COMWUSettings.ScheduledInstallationTime            
            }            
            $obj | Add-Member -MemberType NoteProperty -Name 'Install Frequency' -Value $InstallDay            
            $obj | Add-Member -MemberType NoteProperty -Name 'Install Time' -Value $InstallTime            
            
        } else {            
            # not available on W8            
        }            
        (New-Object -ComObject Microsoft.Update.ServiceManager).services | ForEach-Object {            
            if ($_.IsDefaultAUService) {            
                $OptinGUID = $_.ServiceID             
            }            
        }            
        if ($OptinGUID -eq '7971f918-a847-4430-9279-4a52d1efe18d') {            
            $obj | Add-Member -MemberType NoteProperty -Name "Opted-in Microsoft Update" -Value $true            
        } else {            
            $obj | Add-Member -MemberType NoteProperty -Name "Opted-in Microsoft Update" -Value $false            
        }            
        # return            
        $obj            
    }            
}            
End {}            
}

Here’s what I get on the Windows 7 computer I use at home:
Get-WUsettings output on W7 @home
Here’s what I get on the Windows 8 computer I use at home:
Get-WUSettings output on W8 @home

Rien ne sert de courir, mieux vaut marcher dans la bonne direction

Une fois n’est pas coutume, j’ai d√©cid√© d’√©crire mon premier billet de l’ann√©e √† la date anniversaire de mon blog (qui f√™te ses 1 an) dans ma langue maternelle, la langue de Moli√®re. En ce d√©but d’ann√©e, c’est g√©n√©ralement la p√©riode des bonnes r√©solutions et l’objet double de ce billet est de vous expliquer pourquoi vous ne pourrez pas ignorer ou vous passez de Powershell en 2013 et quels sont les bons r√©flexes √† acqu√©rir pour d√©couvrir et se simplifier la vie en Powershell.

Sans avoir besoin de sortir une boule de cristal, certaines tendances fortes montrent en quoi Powershell sera inévitable et indispensable en 2013:

  • L’ensemble des produits Microsoft est mis sur le march√© en int√©grant Powershell que ce soit directement au coeur de l’OS ou dans les applications de type ‘LoB (Line of Business)’. Ainsi, le client Windows 8 et sa version serveur Windows 2012 int√®grent par d√©faut Powershell 3.0 et sont livr√©s avec un nombre impressionnant de cmdlets (se lit: “commande-let”). L’ensemble de produits Microsoft √† destination des entreprises comme Active Directory, Exchange, SharePoint, Hyper-V et ceux de la gamme System Center, SCOM, VMM, Orchestrator, SCCM, viennent aussi avec un lot important de cmdlet permettant de maintenir et de piloter ces outils en Powershell. Il est m√™me possible d’interagir avec le Cloud public en Powershell sur la plateforme Azure de Microsoft ou celle d’Amazon ou le cloud priv√© fournit par VMware. Cette nouvelle orientation semble √™tre une nouvelle marque de fabrique des produits Microsoft auquel les autres √©diteurs s’empressent d’emboiter le pas.
  • L’ensemble de la plan√®te IT parlent d’automatisation, de ‘devops’ interpr√©t√© comme le rapprochement entre les d√©veloppeurs et les op√©rateurs du SI (admin syst√®me) ou comme une infrastructure sous forme de code (infrastructure as code). Que vient faire Powershell dans tout √ßa ? Eh bien c’est simple, c’est le ciment pour construire ces fondations et/ou ses relations que ce soit au sens propre ou figur√©. C’est √† la fois le langage utilis√© au niveau du code pour atteindre le niveau d’automatisation n√©cessaire dans nos infrastructures et nos applications, mais aussi le langage commun sur le plan technique utilis√© par les d√©veloppeurs et les admins syst√®me et qui de ce fait facilite la communication entre ces deux mondes de moins en moins √©tanches qui ne peuvent plus se permettre de s’ignorer.
  • Concr√®tement, l’int√©r√™t principal de Powershell est de gagner du temps lorsqu’il s’agit de r√©p√©ter une op√©ration ou une t√Ęche qu’il s’agisse de la r√©p√©ter dans le temps ou √† grande √©chelle. Si je dois chercher la liste des utilisateurs dont le compte est verrouill√©, je peux le faire manuellement la premi√®re fois en utilisant une console graphique (la mmc Active Directory) mais si je dois faire cela plusieurs fois par jour ou par semaine, cela devient vite fastidieux, r√©p√©titif et ennuyant. A la place, je peux tirer profit de PowerShell avec cette simple ligne de commande
    Search-ADAccount -LockedOut | Unlock-ADAccount

    ou en construisant un script qui va faire le travail √† ma place. Idem, si je dois faire des modifications sur 3 comptes, comme changer leur date d’expiration, c’est plus facile et rapide de le faire dans une console graphique. Par contre si je dois modifier 2500 comptes, je vais utiliser Powershell pour ex√©cuter cette t√Ęche en boucle de telle sorte √† √©viter les erreurs et le c√īt√© fastidieux de la r√©p√©tition.

  • Powershell c’est aussi vivant, derri√®re l’arbre se cache la for√™t. PowerShell est aussi anim√© par une communaut√© de passionn√©s. Parfois, en plus d’√™tre une passion, c’est m√™me un art de vivre. Voici une liste non exhaustive o√Ļ vous pourrez trouver de l’aide, des informations et entrer en contact avec la communaut√© Powershell et son remarquable travail: Twitter, Scripting Podcast, IRC chat, Powershell.org, Powershell Magazine, Scripting Games, Hey Scripting Guy, Script Gallery, Poshcode.org, des blogs, des flux RSS,…

Maintenant que le d√©cor est plant√©, passons √† la pratique. Vous pouvez √©videmment suivre le conseil d√©livr√© par le titre de mon billet. Cependant, il peut s’av√©rer fort judicieux de se munir d’une carte et d’une boussole dans certains cas. Comme en sport, l’important c’est de participer. Aussi pour prendre un bon d√©part (mieux vaut tard que jamais), je vais √† pr√©sent vous livrer le strict minimum vital √† connaitre avant de se lancer dans l’aventure.

  • Tout est objet. Il ne faut jamais perdre de vue qu’en Powershell tout est objet. Cela signifie en d’autres termes, que chaque objet a des propri√©t√©s et des m√©thodes qu’il est possible d’appeler pour effectuer une action. Powershell √©tant riche, tr√®s r√©guli√®rement, seule une partie des propri√©t√©s sont pr√©sent√©es √† l’affichage. Cela sera bien plus limpide lorsque vous lirez la suite…
  • Comme dans n’importe quel langage, lire l’aide est primordial. Tout √©tant objet, l’aide n’√©chappe pas √† la r√®gle. Bref il est plus que fortement recommand√© d’appliquer et de mettre en pratique quasi religieusement le fameux adage ‘RTFM’ que l’on traduira ici en ‘Read the friendly manual’ pour rester √† la fois politiquement correct et rester proche du sens du mot ‘fucking’ en anglais qui, sans √™tre du Shakespear, ne sert parfois qu’√† mettre l’emphase sur un mot ou une id√©e sans que cela soit n√©cessairement grossier. Pour chercher de l’aide ou m√™me la parser, la parcourir, il suffit d’utiliser la commande Get-Help ou son alias man si vous venez du monde Unix (plus court √† taper), suivi d’un mot cl√©. Cela permet de faire une recherche dans l’aide sur un mot cl√©. Si je tape Get-Help suivi du nom d’une cmdlet, j’obtiens la version courte associ√©e √† cette cmdlet. Au choix, je peux afficher toute l’aide en tapant Get-help [nom de la cmdlet] -Full ou je peux afficher l’aide dans un navigateur en utilisant le commutateur -Online comme argument.
    Les concepteurs ont aussi ajout√© de la compl√©tion qu’il est possible d’utiliser en tapant les premi√®res lettres puis en utilisant la touche tabulation pour faire d√©filer les autres possibilit√©s: Get-[touche Tab]
  • Tout √©tant objet en Powershell, il est possible de faire varier l’affichage et la pr√©sentation avec les cmdlets de mise en forme qui commencent toutes par le verbe Format- . Ces cmdlet de mise en forme servent √©galement √† d√©couvrir les propri√©t√©s qui ne seraient pas affich√©es par d√©faut.
    Get-WmiObject -Class  Win32_OperatingSystem  
    # Pour faire apparaitre l'ensemble des autres propriétés, on fait:
    Gwmi Win32_OperatingSystem | format-list -Property *            
    Gwmi Win32_OperatingSystem |  fl  *

    # Plus fun, pour faire apparaitre des sous-propriétés d'un objet
    $host.UI | format-Custom -Depth 2
    $host.UI | fc -Depth 2            
    

  • Si je ne trouve pas ce que je recherche, c’est que la cmdlet est peut-√™tre pr√©sente dans un module qui n’est pas (encore) charg√©. En powershell version 3.0, les modules sont maintenant automatiquement pr√©charg√©s. M√™me si cela nous facilite la vie, il reste n√©anmoins indispensable d’explorer la listes des commandes disponible dans un module. Dans ce cas, il y a 2 cmdlet √† connaitre. Get-Command (abr√©g√© en gcm ) et Get-Module qu’il est possible d’accompagner de nombreux commutateurs diff√©rents.
    Get-Module -ListAvailable

    gcm -Module PSDiagnostics

    gcm -Verb Get

  • Il convient aussi g√©n√©ralement de d√©couvrir le type d’objet auquel nous avons affaire ou que nous souhaitons manipul√©. Il peut s’agir des objets retourn√©s par une cmdlet (l’output/la sortie) ou m√™me du type d’objets qu’accepte une cmdlet, une fonction ou un param√®tre. Cela peut sembler parfois plus facile √† dire qu’√† faire. Une cmdlet comme Get-Process va retourner une liste d’objet. Il y a une astuce qui nous permet d’interroger le vrai type de chaque objet appartement √† cette liste. Pour ce faire, il suffit de transformer cette liste en tableau en entourant l’expression de @() puis de dire que nous voulons le premier √©l√©ment de ce tableau en y acollant [0] ou le dernier avec [-1]. Voici un exemple concret qui va illustrer mon propos.
    # La cmdlet renvoie un tableau=array            
    @(Get-process).GetType()            
    # Le 1er élément du tableau est un objet process            
    @(Get-process)[0].GetType()            
    # Pour afficher le nom complet du type d'objet            
    @(Get-process)[-1].GetType().FullName


    Idem lorsque j’interroge l’aide, il y a 2 sections importantes sur le type d’objet accept√© en entr√©e par une cmdlet, la section input, et le type d’objet retourn√© en sortie par la cmdlet, la section output. Dans l’exemple ci-dessus qui est l’aide associ√©e √† la cmdlet Get-Process, je peux aussi voir que le param√®tre -Name accepte un tableau de chaines de caract√®res. Il est donc possible de faire:

    Get-process -Name notepad,iexplore

  • Venons-en aux m√©thodes. Pour les d√©couvrir, la cmdlet Get-Member sera votre meilleur atout. Si je reprends mon exemple pr√©c√©dent, il semble pour le moins √©vident que je dois √† pr√©sent interroger les m√©thodes associ√©es √† l’objet de type process. Pour ce faire, j’injecte simplement le r√©sultat comme entr√©e √† la cmdlet Get-Member qui est abr√©g√©e en gm. Concr√®tement cela donne:
    (Get-process)[0] | gm            
    (Get-process)[-1] | Get-Member -MemberType Methods


    Notez que la cmdlet nous affiche aussi le type d’objet auquel nous avons affaire, ce qui nous fait d’une pierre deux coups.

  • Toutes les cmdlets ne sont pas aussi inoffensives que celles dont le nom commence par le verbe Get-. Moins le verbe qui compose la cmdlet semble inoffensif, plus il est vivement conseill√© d’utiliser le commutateur -Whatif qui permet de simuler ce qu’il va se passer. Avec la liste suivante, Restart- , Set-, Stop- , Clear- , Remove- , il semble assez √©vident que l’action r√©alis√©e par la cmdlet peut s’av√©rer dangereuse et effectuer des modifications ind√©sirables.
    Restart-computer -Whatif

    Get-process -Name notepad | Stop-Process -WhatIf

  • Venons-en √† l’un des concepts peut-√™tre les plus difficiles √† appr√©hender dans le langage PowerShell. Rien de tel qu’un peu d’humour pour marquer les esprits et retenir plus facilement ce qui se cache derri√®re cette cl√© de voute que repr√©sente le signe $_. Il n’y a pas si longtemps, le p√®re du langage PowerShell a tweet√© ceci:
    About $_
    Cela se traduit par: objet courant dans le tuyau (pipeline) comme variable à laquelle tu peux faire appel dans les expressions.
    C’est effectivement moins aust√®re mais n√©anmoins proche de sa d√©finition officielle dans l’aide qu’on obtient en tapant:

    get-help about_Automatic_Variables

    $_
    Same as $PSItem. Contains the current object in the pipeline object.
    You can use this variable in commands that perform an action on every
    object or on selected objects in a pipeline.

    C’est aussi l’occasion de comprendre ce qu’est ce tuyau, ce fameux pipeline. En gros, je peux imbriquer plusieurs cmdlet entre elles mais pour qu’elles s’emboitent correctement, il faut parfois faire des petites modifications sur le type d’objet. Commen√ßons par un exemple simple. Disons que je souhaite obtenir la liste de tous les services qui tournent actuellement sur mon ordinateur. C’est aussi le premier exemple cit√© dans l’aide de la cmdlet Get-Service. Comme il n’existe pas de commutateur -Running ou de param√®tre “-Status ‘Running'” qui me permettent de retourner uniquement les services qui sont lanc√©s, je dois faire appel √† la cmdlet Where-Object (abr√©g√© en ‘?’) qui va me filtrer tout √ßa. J’envoie dans le tuyau (le pipeline) en entr√©e un tableau qui me liste l’ensemble des objets de type ServiceController. Pour faire appel √† chaque objet unitaire envoy√© dans ce tuyau, j’utilise ce fameux “$_” qui y fait r√©f√©rence. Je sais que l’objet serviceController a une propri√©t√© ‘Status’ et que celle-ci doit √™tre √©gale √† ‘Running’ lorsque le service est lanc√©. Bref, cela donne:

    Get-Service | Where {$_.Status -eq "Running"}            
    

    Depuis la version 3.0, dans des expressions simples, il est m√™me possible d’omettre √† la fois le fameux ‘$_’ et √©galement les accolades (“curly braces”) qui marquent et d√©limitent une expression. L’expression suivante est donc √©quivalente:

    Get-Service | ? Status -EQ "Running"

    Prenons un second exemple, celui utilisé par Don Jones pour expliquer comment imbriquer des cmdlets. Son exemple est audacieux:

    Get-Service | Stop-Process -WhatIf

    Ceci va majoritairement √©chouer et vous allez obtenir beaucoup d’erreurs qui disent que le nom du service n’a pas √©t√© trouv√© comme nom de processus. Si vous regardez attentivement, vous allez peut-√™tre remarquer certains succ√®s. Tentons de les isoler et de n’afficher que ceux-ci. Le code suivant permet de le faire:

    Get-Service | ? Status -EQ "Running" | ForEach {             
        If ($_.Name -in (Get-process).ProcessName) {            
            Stop-Process -Name $_.Name -WhatIf            
        }            
     }

    Dans chaque expression, le $_ fait r√©f√©rence √† un objet de type servicecontroller issu de la toute premi√®re cmdlet Get-service. Avec une expression Si (‘If’), je v√©rifie juste que le nom du service existe dans la liste des noms de processus. Si c’est le cas, je tente l’arr√™t avec la cmdlet Stop-Process. Le r√©sultat en image:
    Encore une derni√®re pr√©cision un peu pr√©coce mais √† mettre dans un coin de sa t√™te pour plutard. Dans certaines constructions, il arrive que le ‘$_’ repr√©sente une autre objet, notamment dans un bloc de code ‘try {} catch {}’.

    Get-process | ? Name -EQ "notepad" | % {            
     try {            
      Stop-Process -Name $_.Name -ErrorAction Stop            
     } catch {            
      # Ici le $_ représente l'objet ErrorRecord            
      $_ | gm            
      $_             
     }            
    }


    Pour produire cette erreur, j’ai lanc√© notepad avec un ‘run as administrator’ et j’ex√©cute le code ci-dessus dans un process powershell qui n’a pas √©t√© √©lev√©, ce qui fait qu’il √©choue naturellement avec un access denied.

Avec cette petite trousse de survie compos√©e des outils ci-dessus, vous √™tes √† pr√©sent pr√™ts √† partir du bon pied dans la bonne direction. C’est maintenant votre tour de vous lancer dans l’aventure. N’oubliez pas qu’il faut se jeter √† l’eau pour apprendre √† nager et que vous nagerez d’autant mieux que vous pratiquez et vous vous exercez quotidiennement.