Freigeben über


Excluding Known Multi-Reboot Updates during a ZTI Deployment

The following post was contributed by Cliff Jones a Consultant working for Microsoft Consulting Services.

Today's blog post goes behind the scenes to talk about how to prevent installing all published Windows Update's which require multiple reboots to successfully install, during a ZTI deployment Task Sequence. These updates can cause issues as a result of the following behavior; the Task Sequence engine is aware the update requests the initial reboot. Upon boot-up, the Task Sequence engine never initializes and therefore can't be aware that the same update is requiring a second reboot, and thus finally won't make the necessary changes to restart the TS engine again. When the second reboot occurs, during the deployment, the TS will be in an erroneous state, and will cause an error similar to:

"Task Sequence environment not found"

Ideally, these updates should be injected using DISM during the Offline phase of installation, or added during the Build and Capture as the LTI process is not affected by these updates. Either way, a detection and blocking mechanism is needed for your ZTI deployment process.

The following script dynamically query’s the published Microsoft Support page and finds all KB's listed as problematic KB's. Once the list is generated it is up to the administrator to figure out how to block them. This blog post will walk through how to block these updates using MDT as deployment mechanism.

Big thanks to @Dustin Martin for helping in finalizing this script's development.

Below is a standalone version of the script:

$url = "https://support.microsoft.com/kb/2894518"

$i=1 

#Store Source HTML URL

$result = Invoke-WebRequest $url 

#Narrow Source HTML to just Hyperlinks

$elements = $result.AllElements | Where Class -eq "plink" 

#For Each hyperlink

foreach ($link in $elements.innertext)

{

#Find string '/kb/' and get its position

$pos = $link.indexof('/kb/') + 3 

# If '/kb/' not found $pos=-1 +3 = 2

# Not found indicates non KB hyperlink

if ($pos -gt 3)

    {

    #Move the starting position to the start of the KB number and capture all char's to end

    $sOutput = $link.Substring($pos,$link.Length-$pos)

    #Replace '/' and ')' with nothing

    $ExcludeKB = $sOutput.Replace('/','').Replace(')','').Trim()

    #KB Number is stored in $ExcludeKB

    Write-Host $ExcludeKB

    }

$i++ 

Script Functionality

Microsoft has committed to keeping the support page containing the list of problematic updates, up-to date. This support page is located here: https://support.microsoft.com/kb/2894518.

The first function of this script is to go out and store all source code and HTML elements to a variable to be used for processing later.

Continuing with the script break down, we can see our WebRequest call, which stores the source code of the support page in a variable called $result.

$result = Invoke-WebRequest $url 

If you echo $result you will see an enormous amount of information, numerous elements, housing their respective properties each contain the corresponding values.

In order to reduce to this down into more usable objects, we only include the elements that have a class called "Plink". Plink is an HTML item which contains a hyperlink. Using this class will include ALL hyperlinks contained within the page. In our case this contains ALL the problematic KB article pages, with some additional links.

$elements = $result.AllElements | Where Class -eq "plink"

Echo'ing different properties of the object called $elements, we can see pertinent information. To find how many hyperlinks are contained within the class we can perform a count on the number of "PLINK". Powershell objects ($elements) automatically contains the .Count property:

image 

Because $Elements is an object, and contains more than 1 item, we must iterate through each item. Taking the count from above into consideration, we can echo specific $element items to view all the associated properties and their values. The first item starts at [0] and goes to [12], since there are 13 total. Below we can see the first PLINK item's [0] data to work with:

 image

By observing $elements[0], we now know the specific property to enumerate during the entire item iteration. In the end this property is used to generate the entire exclude KB list.

In PowerShell a ForEach loop automatically loops through all of a collection of items. In the current usage the collection is $elements with each item [X] assigned to the variable $link. Each time the loop occurs the $link is reset, meaning it only ever contains 1 value equal to the respective $elements[X].innertext. This string is then to be used in the processing section of the foreach loop.

foreach ($link in $elements.innertext)

{

*PROCESSING*

}

Each string $link to be processed is assumed to be a hyperlink, and the desired KB number is at the very end of the string. These two made assumptions must be validated, thus ensuring script reliability. For this example we only require the KB number, and nothing more.

To begin the necessary string cleansing, the position in the string, to begin processing must be known. For each line of $Elements.innerText or $link=( https://support.microsoft.com/kb/XXXXXXXX ), we will set the variable $POS to the numerical position of the start of '/kb/'. The next 3 characters will always be 'kb/', knowing this simply adding 3 to move our position to the final / after KB, creates our desired starting point.

This position number is important because not only does it provide our initial modification location but also serves as a validation test. Remember earlier when the PLINK class was used as a filter, and it included all items with a hyperlink and not just instances of the KB hyperlink? By searching for '/KB/' specifically, all non KB hyperlinks are eliminated. If '/KB/' is not found $POS will equal -1, again, meaning '/KB' was not found.

Below is an example of the index number for items [1] and [12]:

image

Using an If statement, only valid KB hyperlink's will have an index number ($POS) greater than 3.

if ($pos -gt 3)

There are just a few more items to take into consideration when cleansing the KB hyperlink string. At this point the string is just '/#######*'. The document says * when in reality there are two different possibilities, which are a '/' or a ')'. It is inconsistent but simple enough to correct either. Using Powershell's substring method, provide a string, the starting point, and the length or number of characters to save.

In our example we use $link which currently is ( https://support.microsoft.com/kb/2984976 ) , move our starting point to $pos, or 32 characters in from the left, and save all the characters from the starting point to the end. We can dynamically control the characters being saved, (length) by taking the total string length and subtracting it from our starting point $pos and that equals the remainder of the string.

$link.Substring($pos,$link.Length-$pos)

When we do this and echo our sOutput variable we can see there is just one more tweak to do to completely cleanse the KB hyperlink. Below is that output:

 image

The last item is to remove the '/' and ')' from each of the strings and the string cleansing will be complete. Luckily Powershell provides a Replace method much like the substring method. The replace method requires a string, the character to look for, and what to replace that character with.

We will simply replace both the '/' and ')' with nothing, and then ensure all trailing blank spaces are removed. Below is our needed replace scenario example:

$sOutput.Replace('/','').Replace(')','').Trim()

And finally the corresponding output:

image

At this point you all the KB numbers have been found and properly sanitized for use. Once sanitized how can this be put to use? Well of course we need to integrate it into the ConfigMgr deployment task sequence :).

Deployment

Some modifications needed to be made to the script in-order to make it usable during deployment. The overall script functionality is exactly the same as above, the modifications were made to make the script more memory efficient and functionally able to utilize Task Sequence environment variables.

CheckBadKB.ps1

 $i=1

$tsEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment

$url = "https://support.microsoft.com/kb/2894518"

$result = Invoke-WebRequest $url 

$result.AllElements | Where Class -eq "plink" | ForEach-Object { 

$pos = $_.innertext.indexof('/kb/') + 3 

#If Valid KB Hyperlink

if ($pos -gt 3)

{

        #String Cleansing, final ExcludeKB = 1234567

        $ExcludeKB = $_.innertext.Substring($pos,$_.innertext.Length-$POS).Trim().Replace('/','').Replace(')','')    

        #This Write-Host can be found in CheckBadKB.log file.  

        #Run Powershell script step will output automatically to selfcreated log file

        Write-Host "WUMU_ExcludeKB$i=" $ExcludeKB

        $tsEnv.Value("WUMU_ExcludeKB$i") = $ExcludeKB

}

$i++ 

}

Task Sequence Modifications

This deployment section of the blog post is intended to provide an example methodology to prevent these problematic patches from applying during the ZTI TS. The components of this section should provide an 80% completed solution with the remaining 20% being requirements gathering and final implementation. The proper process to prevent these updates from failing your ZTI deployment, will vary from customer to customer based on their requirements. 

In this blog's example we have integrated MDT with Configuration Manager and no longer use the default Configuration Manager step "Install Software Updates". Instead the MDT script ZTIWindowsUpdate.wsf is called.

A few notes about using this demo methodology in production:

  • This is not a supported Microsoft scenario, Microsoft will require the default Install Software Update Step be used, if problems arise.
  • This will completely bypass the Software Update Point, and each client will download the updates either from a Standalone WSUS Server (if specified) or from Microsoft's public Windows Update site.

The CheckForBadKB.ps1 script currently needs to run Online and the client must have internet access. Below our two figures show how our example utilizes the example prevention process. 

Figure 1 shows a 'Run Powershell Script' step to run immediately before the demonstration step MDT Install Software Updates.

 image

Figure 2 shows a 'Run Command Line' step to run the ZTIWIndowsUpdate.wsf script to apply Windows Updates

image 

Verifying on Client Side

Once everything has been configured we need to verify on our client side that things are working. Once the CheckBadKB script runs it will generate a log file for itself, located in the normal SMS TS Log path locations. This CheckBadKB.log file should indicate all the KB's added to the exclude list. Below Highlighted in bold is that process happening:

CheckBadKB.log

<![LOG[WUMU_ExcludeKB1=]LOG]!><time="18:06:53.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[ ]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[2984976]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[WUMU_ExcludeKB2=]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[ ]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[2981685]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[WUMU_ExcludeKB3=]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[ ]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[2966034]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[WUMU_ExcludeKB4=]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[ ]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[2965788]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[WUMU_ExcludeKB5=]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[ ]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

<![LOG[2920189]LOG]!><time="18:06:54.000+000" date="11-28-2014" component="TaskSequencePSHost" context="" type="1" thread="" file="TaskSequencePSHost">

Once the Windows Update step starts to run, if we monitor the ZTIWindowsUpdate.log file we can see our targeted KB's, being skipped during the update installation process. Below in bold is that process occurring:

ZTIWindowsUpdate.log

<![LOG[INSTALL - b1d4e91a-26a7-4215-b0d5-7819b7ee125a - Windows Malicious Software Removal Tool for Windows 8, 8.1 and Windows Server 2012, 2012 R2 x64 Edition - November 2014 (KB890830) - 31 MB]LOG]!><time="21:52:17.000+000" date="11-28-2014" component="ZTIWindowsUpdate" context="" type="1" thread="" file="ZTIWindowsUpdate">

<![LOG[ SKIP - c4f97de4-bdad-4d50-9a24-07d241040b0f - Security Update for Windows 8.1 for x64-based Systems (KB2920189) - 10 MB]LOG]!><time="21:52:17.000+000" date="11-28-2014" component="ZTIWindowsUpdate" context="" type="1" thread="" file="ZTIWindowsUpdate">

<![LOG[INSTALL - fb3da1f2-6a6e-4f4a-b25c-a05af3974acc - Security Update for Internet Explorer Flash Player for Windows 8.1 for x64-based Systems (KB3018943) - 17 MB]LOG]!><time="21:52:17.000+000" date="11-28-2014" component="ZTIWindowsUpdate" context="" type="1" thread="" file="ZTIWindowsUpdate">

 

Disclaimer: The information on this site is provided "AS IS" with no warranties, confers no rights, and is not supported by the authors or Microsoft Corporation. Use of included script samples are subject to the terms specified in the Terms of Use .

Comments

  • Anonymous
    January 01, 2003
    I don't think that's the issue in my case. WMF 4 is included in our reference image. Also, looking at the error, it doesn't look like invoke-webrequest is an unknown command, but rather that it tried to run, but can't because IE isn't ready yet.
  • Anonymous
    January 01, 2003
    Since Powershell 2.0 does not contain Invoke-WebRequest, you can here is a work around:
    -------------------------------------
    $i = 1
    $WebClient = New-Object System.Net.WebClient
    $tsEnv = New-Object -ComObject Microsoft.SMS.TSEnvironment
    $WebClient.DownloadString("http://support.microsoft.com/kb/2894518") |
    Select-String "/kb/([0-9]{7,8})" -allmatches |
    Select-Object -ExpandProperty Matches |
    Select-Object -ExpandProperty Groups |
    where-object { -not ( $.Groups ) } |
    Select-Object -ExpandProperty Value -unique |
    where-object {
    write-verbose ("WUMU_EXCLUDEKB{0:D3}={1}" -f $i,$
    )
    $tsEnv.Value("WUMU_ExcludeKB$i") = $_
    $i++
    }
    -------------------------------------
  • Anonymous
    January 01, 2003
    Thank you for sharing this! If you want to use the -UseBasicParsic switch and not set the DisableFirstRunCustomize registry value you can:

    Change
    $result.AllElements | Where Class -eq "plink"
    to
    $result.Links

    Then replace instances of "innertext" to "href"
  • Anonymous
    January 01, 2003
    Thanks for the tips on Figure 2 everyone, fixed now :).
  • Anonymous
    January 01, 2003
    I added this to a windows 7 OSD task sequence and ran a test deployment. When it finished, the CheckBadKB.log shows the following:

    ------------------------------------------------------------
    The response content cannot be parsed because the Internet Explorer engine is not available, or Internet Explorer's first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again.
    NotImplemented: (:) [Invoke-WebRequest], NotSupportedException
    TSHOST: Script completed with return code 0
    ------------------------------------------------------------

    I have the script inserted at the same point in the TS as in the image, but it seems like maybe the environment isn't yet ready to meet the requirements of the script at that point? I did try adding the -usebasicparsing switch to the invokewebrequest command, but I think the rest of the script depends on the fully parsed data. . .
  • Anonymous
    January 01, 2003
    Invoke-WebRequest is a Powershell 3.0 cmdlet, please ensure Powershell 3.0 is installed on Windows 7. This is not an issue on Windows 8 because Powershell 3.0 is already present.
  • Anonymous
    January 01, 2003
    The comment has been removed
  • Anonymous
    March 10, 2015
    I didn't test that yet, but if it works (what i expect) it really makes our life easier :) Thanks a lot for sharing !
  • Anonymous
    March 11, 2015
    Figure 2 seems, at the moment, very much like Figure 1.
  • Anonymous
    March 11, 2015
    FYI: The last image is a bit off, should probably be a screendump of the "MDT Install Updates"-step...
  • Anonymous
    March 11, 2015
    Figure 2 is the wrong image...
  • Anonymous
    March 11, 2015
    thanks
  • Anonymous
    March 12, 2015
    Looks like IE needs "first-launch configuration". I guess you can set few registry key to avoid this.
  • Anonymous
    March 24, 2015
    What about Offline environments that have no internet connection?
  • Anonymous
    October 13, 2015
    Sadly it appears this not longer works. Any luck for anyone?
  • Anonymous
    November 19, 2015
    It's not working for me neither.
  • Anonymous
    January 24, 2016
    Not working here, using Invoke-WebRequest or the legacy .NET method. If you export the result, you see the page loads but you get a 'JavaScript is disabled' error, and it asks you to reload the page.