Formation PowerShell (TP)

En cherchant une cas concret pour des travaux pratiques lors d’une formation PowerShell dispensée à quelques collègues, je suis tombé sur une pépite.

Cette idée était tellement sympa et stimulante qu’il m’a paru impossible de ne pas la partager avec la communauté francophone.

J’ai fait de cet exercice sur la gestion des modes d’alimentation un petit cookbook illustré d’environs une trentaine de pages, disponible ici .

Voici le code qui accompagne ce petit cookbook. Il est disponible sur ce lien

C’est pas tout! Une occasion unique s’offre à vous :D
Vous pouvez apprendre PowerShell DSC (Desired State Configuration) le 25 et 26 février en live sur la Microsoft Virtual Academy.
C’est ​Jeffrey Snover et Jason Helmick qui dispensent ces 2 cours.

Pour ce faire, il faut:

Follow-up: configuring a proxy with DSC

While working on a custom DSC (Desired State Configuration) resource that forces the Windows Update Agent to opt-in to Microsoft Update, I found a major caveat with my previous blog post about configuring a proxy per machine with DSC.

My server wouldn’t opt-in to Microsoft Update (MU) and when it tried, it last ~20 seconds which is an excessive amount of time. Under my user account, the registration takes less than a second.

Here’s the code I’m using inside the DSC resource and at command prompt.

(New-Object -ComObject Microsoft.Update.ServiceManager).
AddService2('7971f918-a847-4430-9279-4a52d1efe18d',7,"");

I couldn’t understand why and started a procmon trace.

The WindowsUpdate.log file acknowledged that it lasts 20 seconds and indicated that it doesn’t use the proxy set per machine :(

The procmon trace indicated that the Windows Update service was looking for the WinHttpSettings value and couldn’t find it

My bad :(
That’s what I actually set in my DSC configuration as I set the WinHttpSetting as absent.

To fix it, I duplicated the DefaultConnectionSettings item and set the exact same value for the WinHttpSettings item

And now the registration is back to normal:

I also found a second problem while reading the verbose output when applying the DSC configuration :(

[ Start Test ] [[Registry]ProxyAddressPerMachineDefaultConnectionSettings]
VERBOSE: [MyComputerName]: [[Registry]ProxyAddressPerMachineDefaultConnectionSettings] Registry key value ‘HKLM:\software\microsoft\windows\currentversion\internet settings\connections\DefaultConnectionSettings’ of type ‘Binary’ does not contain data ‘46000000040000000300000015…0′

The DSC Test-TargetResource function always returned false because the registry value set in the first place was somehow “autocorrected”.
The fact that the Test-TargetResource always returns false isn’t normal.

I recommended in my original post to extract the value from the registry like this:

$regkey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections"
-join (
(Get-ItemProperty -Path  $regkey -Name DefaultConnectionSettings).DefaultConnectionSettings |
 Foreach-Object { '{0:X2}' -f $_ })

…which is the wrong way and the root cause of the above behavior I described.

My bad :( Sorry about that.

I fixed my issue by capturing correctly the hexadecimal value from the registry :D

$regkey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections"
-join (
(Get-ItemProperty -Path  $regkey -Name DefaultConnectionSettings).DefaultConnectionSettings |
 Foreach-Object { '{0:X1}' -f $_ })

Backporting the Get-FileHash function

Not later than this morning, my colleague needed to check the checksum of a file but didn’t got any PowerShell script to easily get it but got the 11-years old fciv.exe instead.

To get rid of the fciv.exe utility, there’s actually a Get-FileHash function available as of PowerShell version 4.0 built-in the Microsoft.PowerShell.Utility module.

Good news! As it’s a function, it’s directly accessible from the Microsoft.PowerShell.Utility.psm1 file and the effort to make it work on PowerShell version 2.0 on Windows 7 isn’t as monumental as we can think.

The first step is to open the module on a Windows 10 Technical Preview in the ISE to copy the first 2 functions Get-FileHash and GetStreamHash.

and paste them into the ISE of a PowerShell version 2.0

NB: We can see that the syntax highlighting is broken.

The second step consists in making minor changes to make these functions compatible with PowerShell version 2.0

  • On lines 5,9 and 14: replace the Mandatory statement by Mandatory = $true.
    It will restore the syntax highlighting and avoid the following error message
  • # from
    [Parameter(Mandatory, ParameterSetName="Path", Position = 0)]
    # to
    [Parameter(Mandatory=$true, ParameterSetName="Path", Position = 0)]
    
  • On line 3, remove the HelpURI from the CmdletBinding statement and avoid the following error message
  • # from
    [CmdletBinding(DefaultParameterSetName = "Path", HelpURI = "http://go.microsoft.com/fwlink/?LinkId=517145")]
    # to
    [CmdletBinding(DefaultParameterSetName = "Path")]
    
  • On lines 40 and 44, fix the Foreach-Object statement and avoid the following error message
  • # from
    $pathsToProcess += Resolve-Path $Path | 
    Foreach-Object ProviderPath
    # to
    $pathsToProcess += Resolve-Path $Path | 
    Foreach-Object { $_.ProviderPath}
    
  • On line 62, remove the type PowerShell isn’t aware of when the file doesn’t exist
  • # from
    $errorMessage = [Microsoft.PowerShell.Commands.UtilityResources]::FileReadError -f $FilePath, $_
    # to
    $errorMessage = 'FileReadError {0}:{1}' -f $FilePath, $_
    

That’s all folks 8-)

You can see the result (stored as gist file)

DSC: configuring a proxy

What’s the first thing you do when you configure a new server?
After configuring the network stack, the time and allowing remote desktop, I make sure that the server has access to Internet so that all the other operations (like downloading drivers, packages, Windows updates, activating Windows,…) that depend on this link run smoothly.

In a corporate environment, accessing the Internet is usually done through a proxy server.
I’ll share with you an old trick I’m using since Windows XP: how to configure a proxy per machine with Desired State Configuration (DSC) and avoid other admins messing with it :D

You can configure proxy settings via

  • a response file (unattend.xml) when the computer is provisioned with Microsoft-Windows-IE-ClientNetworkProtocolImplementation component
  • Group policies
  • That’s out-of-scope as we don’t know if the machine is domain or workgroup joined

  • IE branding
  • Remember the IEAK, it’s based on a INS file located in C:\Program Files (x86)\Internet Explorer\Custom and some registry settings

    The problem with this approach is that you need to indicate the version of Internet Explorer in the INS file. That’s why it’s not a suitable way to proceed.

  • the Registry
  • This method doesn’t depend on the version of Internet Explorer and whether the machine is workgroup or domain joined. It’s thus the most suitable way to go :)

I’ve created 5 small DSC configurations to illustrate 4 basic scenarios and 1 to restore to user based proxy settings. (Note that you mix some scenarios together to match your needs and your environment configuration)

  • NoProxy.ps1 will define proxy settings per machine and configure it to access directly Internet
  • ProxyAutodetect.ps1 will define proxy settings per machine and let autodetect enabled (~default user config but per machine)
  • ProxyURL.ps1 will define a proxy configuration script URL (set to http://myproxy.fqdn/proxy.pac in my example below)
  • Proxy.ps1 will define a proxy address set to myproxy.fqdn on port 8888 for every protocols and bypass proxy server for local addresses
  • RestorePerUserProxy.ps1 will delete all per machine settings and rely back on user settings

(I’ve stored the samples as Gist files, so that you can use the OneGet Gist provider made by Doug Finke to get them ;-) )

If you want to use these configurations, you may need to modify my sample configurations and replace the binary value for both DefaultConnectionSettings and SavedLegacySettings. You actually need to capture them on a working computer where you configured manually Internet Explorer settings for your environment.
Then you can extract the binary value from the HKCU hive with the following code:

$regkey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections"
-join ( 
(Get-ItemProperty -Path  $regkey -Name DefaultConnectionSettings).DefaultConnectionSettings |
 Foreach-Object { '{0:X2}' -f $_ })
-join ( 
(Get-ItemProperty -Path $regkey -Name SavedLegacySettings).SavedLegacySettings |
 Foreach-Object { '{0:X2}' -f $_ })

Here are the 5 small DSC samples with what you get in UI as a result. Enjoy 8-)

  • NoProxy.ps1

  • ProxyAutodetect.ps1

  • ProxyURL.ps1

  • Proxy.ps1

  • RestorePerUserProxy.ps1

Retrieving scheduled tasks history

I had to check the history of custom scheduled tasks after OS upgrades on remote sites servers.

The scheduled task appeared to be working before the upgrade, looked good after the upgrade but was miserably failing at launch time:

Task Scheduler failed to load task “\My TaskName” at service startup. Additional Data: Error Value: 2147944189.
Whenever I launched the task manually, the schtasks.exe returned
ERROR: The system cannot find the file specified.

The solution consisted in deleting the task and creating it back. Creating it by overwriting the existing one didn’t make it actually.

I’ve used PowerShell to track precisely where the task was failing and where it succeeded after the above fix or not.
Because the bandwidth was quite low for the majority of remote sites servers, I had to limit the number of events and properties returned over remoting.

I’ve created the following XML query used with the Get-WinEvent cmdlet to query the history of my custom scheduled task (named ‘My TaskName’ below) in fairly decent amount of time.

Get-Content servers.txt | ForEach-Object { 
 $pc = $_
 try {
  invoke-command -ComputerName "$($_).my.fqdn" -ErrorAction Stop -ScriptBlock {
   try {
    $events = @(
     Get-WinEvent  -FilterXml @'
     <QueryList>
      <Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational">
       <Select Path="Microsoft-Windows-TaskScheduler/Operational">
        *[EventData/Data[@Name='TaskName']='\My TaskName']
       </Select>
      </Query>
     </QueryList>
'@  -ErrorAction Stop -MaxEvents 2
   ) 
   } catch {
     Write-Warning -Message "Failed to query $($env:computername) because $($_.Exception.Message)"
   }
   if ($events) {
    $events | Select MachineName,TimeCreated,Id,TaskDisplayName
   }
  } # end of scriptblock
 } catch {
  Write-Warning -Message "Failed to contact $pc because $($_.Exception.Message)"
 }
} | 
Select MachineName,TimeCreated,Id,TaskDisplayName |
Format-Table -AutoSize

Here is what it looks like when I query the history over the last 24 hours
(replace line 11 with this one in the above snippet and remove -MaxEvents 2 on line 15)

*[EventData/Data[@Name='TaskName']='\My TaskName'] and *[System[TimeCreated[timediff(@SystemTime) &lt;= 86400000]]]

Cleanup WSUS

I’ve never had to perform a WSUS cleanup for years, shame on me ;-)

Lawrence Garvin, who answers all the WSUS maintenance related questions on the patchmanagement.org mailing list made me change my mind recently.

His post explains what are the benefits of performing maintenance tasks on a regular basis:

Source: http://marc.info/?l=patchmanagement&m=142075193424332&w=2

Having WSUS built-in Windows 2012 R2 servers makes it very easy with PowerShell :-D

Here’s what I do a few days later, after performing my usual Patch Tuesday duties (~synchronize and approve required updates):

  • Step 1: Decline all updates that have been superseded and not approved
$UpdateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$allupdates = (Get-WsusServer).GetUpdates($UpdateScope)
$allupdates | Where {
 ($_.IsSuperseded) -and
 -not($_.isApproved) -and
 -not($_.isDeclined)
} | ForEach-Object -Process {            
    $_.Decline()
}
  • Step 2: Use the built-in cmdlet to perform the maintenance and display results
$cleanupResults = Get-WsusServer | 
Invoke-WsusServerCleanup -CleanupObsoleteUpdates `
-CleanupUnneededContentFiles -CompressUpdates `
-DeclineExpiredUpdates -DeclineSupersededUpdates:$false -Verbose

$cleanupResults
'Diskspace Freed: {0:N2} GB' -f (
(($cleanupResults | sls -Pattern "^DiskSpace").Line -split ":")[1] / 1GB 
)

To summarize, I’ve now:

  • Updates being displayed faster in the console, when I select ‘Any Except Declined’
  • Compressed updates (less to transport over the wire)
  • Made some free space on the server (~ 1,5 GB in my case, the first time)
  • A cleanup maintenance task performing faster next month

PowerShell and WSUS, that rocks! – no doubt 8-) and a huge thanks to Lawrence Garvin for his excellent input on this subject :-D

Quicktips: is a reboot required after Windows updates installation

I’ve came across a nice tips when looking for something about the Windows Update Agent (WUA) API.

Let’s say you’ve performed updates installation but forgot to track the global result of the installation process.

There’s actually a way to query it afterward :-D

(New-Object -ComObject "Microsoft.Update.SystemInfo").
RebootRequired

You can have it as a simple function as well:

Function Test-WUARebootRequired {
    try {
        (New-Object -ComObject "Microsoft.Update.SystemInfo").RebootRequired
    } catch {
        Write-Warning -Message "Failed to query COM object because $($_.Exception.Message)"
    }
}
Test-WUARebootRequired