SCCM: In-Place Upgrade Restart Computer Issue

At first I wasn’t going to post anything about this…then I swallowed my pride and hope this would assist the “greater good”.

We went through TAP with Windows 10 and (of course) used the Upgrade Task Sequence Sample that Microsoft provided. It never really quiet worked right, but for our little pilot (~100 machines) it did it’s job. Recently, we went through our big jump to SCCM 1606 directly from 2012 R2 SP2 and now that we were “supported” with In-Place Upgrades I wanted to go through and update the Task Sequences we had been using to an official process that we could safely offer out the remaining 60k+ systems still running Windows 10 RTM and below.

I updated the Task Sequence, got all the custom steps in place where they needed to be but saw failure after failure. Digging in the logs I saw this:

Execution engine result code: Reboot(2)

Oddly enough this was coming from a “Restart Computer” step and as we all know Windows error 2 typically means “File not found”. Even more odd, if I’d reboot the computer, the Task Sequence would pick back up and resume right where it left off. I ran through various other workarounds including a “Run Command Line” doing a shutdown -r, as well as trying Variable dumps, procmon traces, etc…

When I brought up the issue to some of our SCCM contacts at Microsoft, they too were clueless except for Rob’s comment: “I know they changed some of the reboot handling with the new client in 1606…”. Knowing that these test systems probably didn’t have the new client, I reverted the OS, and what do you know… Still running the 2012 R2 SP2 client…I updated the client, re-ran the In Place Upgrade Task Sequence and was met with 100% success!

It’s one of those things you feel so stupid for missing, yet you know you’ll think twice as hard the next time! Since I wasn’t able to find this via any normal search engine, I’m hoping you all are much smarter than I am! Hopefully this saves someone some troubleshooting!

 

Enjoi!

Task Sequence Powershell Module

Awhile ago I submitted a request to Microsoft for a built-in Powershell Module during the Task Sequence. Mostly for basic things like getting and setting variables, but once I noticed what all the COM Objects exposed, I wanted to take this a step further.

Primarily you’ll have 2 COM objects you can create during a Task Sequence: Microsoft.SMS.TSEnvironment (getting/setting variables) and Microsoft.SMS.TSProgressUI (manipulating the Progress and messages). I’ve uploaded the module for you all to use at my github. I’ll run though some of the details below including visuals for the TSProgressUI items. Some, if not most, of these are-for lack of a more appropriate label-useless; but can still be fun to mess around with!

https://github.com/ndswanson/TaskSequenceModule

Microsoft.SMS.TSEnvironment

Confirm-TSEnvironment

This function is not exported, as it is used within the module to verify that the Microsoft.SMS.TSEnvironment COM Object is loaded before attempting any operations.

Get-TSVariables

Calls “GetVariables()” on the COM Object. This will return an array of all Task Sequence Variable NAMES only.

Get-TSValue

Calls “Value()” on the COM Object to return the value of a specific variable. the “Name” Parameter should be the Task Sequence Variable you want the value for.

Set-TSVariables

Assigns a value to a specific variable. A Boolean return indicates success. Note this is wrapped in a try/catch in the event you attempt to set a read-only variable.

Get-TSAllValues

Returns an object of all Task Sequence Variables and their assigned Values.

Microsoft.SMS.TSProgressUI

Confirm-TSProgressUISetup

This function is not exported, as it is used within the module to verify that the Microsoft.SMS.TSProgressUI COM Object is loaded before attempting any operations.

Show-TSActionProgress

Calls “ShowActionProgress()” on the COM object. This is the function I use 99.9% of the time for UI related actions. This will add the “second” progress bar and update the Text for that progress bar as well as fill the progress meter as you see fit. The neat thing here, is that the “Step” and “MaxStep” will automatically show a “100%” representation. For example, if i have 8 of 400 complete, I don’t have to do the extra scripting to pass in “2” and “100”. I can pass in 8, and 400, and the COM Object does the math for us.

There are other parameters here with “ShowActionProgress()”. With this function, those use the built-in variables “designed” to be used here. If you want to extend this function to allow those additional parameters, feel free. Personally, I don’t find them useful enough to include.

Step Param set to 5, MaxStep Param set to 10.
Show-TSActionProgress

Close-TSProgress

Calls “CloseProgressDialog()” on the COM object. Closes or “hides” the progress bar window. This will automatically reappear once the current step has completed, or if you call one of the “show” functions for the UI.

Show-TSProgress

Calls “ShowTSProgress” on the COM object. This function manipulates the “Top part” of the progress bar. If, for whatever reason, you wanted to manipulate the progress bar without adding the “second/bottom” bar, this is what you should use.

Step Param set to 5, MaxStep Param set to 10.
Show-TSProgress

Show-TSErrorDialog

Calls “ShowErrorDialog()” on the COM object. This wills how the typical “Error in the Task Sequence” Window. There are parameters for the error title, error message/details, exit code, a timeout, and a force reboot. The force reboot will trigger a reboot after the timeout (in seconds) has expired.

Show-TSErrorDialog

Show-TSMessage

Calls “ShowMessage()” on the COM Object. This essentially shows a MessageBox allowing you to specify the button configuration. The interesting (and somewhat useless) part of this, is that you do not receive a return value. If you want a simple “OK” message box, this should work, otherwise I’d recommend using a Normal MessageBox.

Button Options (Type Parameter):

  • 0 = OK
  • 1 = OK, Cancel
  • 2 = Abort, Retry, Ignore
  • 3 = Yes, No, Cancel
  • 4 = Yes, No
  • 5 = Retry, Cancel
  • 6 = Cancel, Try Again, Continue

Type Param set to 0.
Show-TSMessage

Show-TSRebootDialog

Calls “ShowRebootDialog()” on the COM Object. This will allow you to present a custom reboot window. I would recommend against this, as untimely reboots during a task sequence can cause some issues. This is essentially what causes the issue where Updates cause Double Reboots during OSD. If you do need a reboot after a step, either inset a restart computer step in your task sequence or just have your script exit “3010”.

Typically OrganzationName will use the TS Variable “_SMSTSOrgName”.
Show-TSRebootDialog

Show-TSSwapMediaDialog

Calls “ShowSwapMediaDialog()” on the COM Object. If you’ve ever used standalone media that was broken up into multiple CD/DVD sets (or played a Final Fantasy on Playstation), you’ve seen this one. This is a message indicating for you to swap media, letting you know what disk number to insert.

Show-TSSwapMediaDialog

 

Enjoi!

Configuration Manager: Content Location Requests

Not that long ago I was challenged with the task of writing a piece of automation for a ConfigMgr Client to check if a specific package was available in it’s boundary; or commonly referred to as a “Content Location Request”. All I was able to find was Robert Marshall’s [MVP] postings for Content Location Request (CLR) Generator (and the Console Edition). The hints at the ConfigMgr SDK we’re clear, so I went ahead and installed it.

To get a jump start I began looking for examples and yet again found a post by Robert Marshall.

The best part here is this works with SCCM 2012 AND the latest Technical Preview that Microsoft has released for Configuration Manager vNext!

So, here we go! Content Location Request’s using C# or Powershell!

With the ConfigMgr SDK installed we’ll be focusing on the Client Messaging SDK: C:\Program Files (x86)\Microsoft System Center 2012 R2 Configuration Manager SDK\Samples\Client Messaging SDK. Inside this folder you’ll see a Sample.cs file and help file (most important!). if you decide to go beyond what I do here, you’ll want to keep the help file close by! Navigating up and in the Redistributables folder you’ll find the Microsoft.ConfigurationManagement.Messaging.dll.

Loading Files

If using Visual Studio, add a reference to the Messaging dll and the appropriate using reference. While you’re at it add a using statement for System.Diagnostics if one does not already exist.

C#

using System.Diagnostics;
using Microsoft.ConfigurationManagement.Messaging.Framework;
using Microsoft.ConfigurationManagement.Messaging.Messages;
using Microsoft.ConfigurationManagement.Messaging.Sender.Http;

Powershell

[void] [Reflection.Assembly]::LoadWithPartialName("System.Diagnostics")
[void] [Reflection.Assembly]::LoadFile("c:\Microsoft.ConfigurationManagement.Messaging.dll")

When using the “LoadFile” method, you’ll need to put in the full path (no variables). In most cases this is an issue, especially if including the dll within the script path. Best way I’ve found is to us the Add-Type cmdlet (where a variable populated path is allowed) in a try block, then have a catch statement to copy the file and then call LoadFile. If using this in WinPE, you’ll NEED to do this due to the loadFromRemoteSouces default setting.

Powershell if anticipating WinPE:

$Script:CurrentDirectory = Split-Path -Parent $MyInvocation.MyCommand.Definition

try
{
   Add-Type -Path "$Script:CurrentDirectory\Utilities\Microsoft.ConfigurationManagement.Messaging.dll" | out-null
}
catch
{
   Copy-Item -Path "$Script:CurrentDirectory\Utilities\Microsoft.ConfigurationManagement.Messaging.dll" -Destination "x:\Microsoft.ConfigurationManagement.Messaging.dll" | Out-Null
   [void] [Reflection.Assembly]::LoadFile("x:\Microsoft.ConfigurationManagement.Messaging.dll") | out-null
}
[Reflection.Assembly]::LoadWithPartialName("System.Diagnostics")

Setting up Objects

Now that we have the necessary references and using statements we’ll need to create the objects used through the request:

C#:

HttpSender hSender = new HttpSender();

ConfigMgrContentLocationRequest clr = new ConfigMgrContentLocationRequest();

ConfigMgrContentLocationReply cmReply = new ConfigMgrContentLocationReply();

Powershell:

$httpSender = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Sender.Http.HttpSender

$clr = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ConfigMgrContentLocationRequest

$cmReply = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationReply

Setting Up the Request

Now with our objects setup, we can begin formulating the request. The first thing we’ll need to do is run the “Discover” method of the ConfigMgrContentLocationRequest object. Essentially this grabs the systems IP Addresses, Current AD Site, AD Domain and AD Forest.

Nothing too difficult right? Well, not exactly! The AD Domain and AD Forest discovery uses the ADSI COM object. Still no problem though, right? Well, not if you intend to use this within WinPE. We’ve got 2 options then: (1) Add the ADSI Plugin to our WinPE image (Thanks to Johan Arwidmark), or (2) build a try catch.

Since the ADSI plugin is not included with WinPE and not supported by Microsoft, we’ll just use a simple try/catch. However, there’s another catch, we’ll need to specify the domain and forest strings.

C#:

private string myDomain = "mydomain.com";
private string myForest = "myForest.com";

try
{
   clr.Discover();
}
catch
{
clr.LocationRequest.ContentLocationInfo.IPAddresses.DiscoverIPAddresses();
   clr.LocationRequest.ContentLocationInfo.ADSite.DiscoverADSite();
   clr.LocationRequest.Domain = new ContentLocationDomain() { Name = myDomain };
   clr.LocationRequest.Forest = new ContentLocationForest() { Name = myForest };

}

Powershell:

[string]$myDomain = "mydomain.com"
[string]$myForest = "myforest.com"

try
{
   $clr.Discover()
}
catch
{
   $clr.LocationRequest.ContentLocationInfo.IPAddresses.DiscoverIPAddresses()           $clr.LocationRequest.ContentLocationInfo.ADSite.DiscoverADSite()
   $clr.LocationRequest.Domain = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationDomain
   $clr.LocationRequest.Domain.Name = $myDomain
   $clr.LocationRequest.Forest = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationForest
   $clr.LocationRequest.Forest.Name = $myForest
}

Next we’ll need to setup the parameters for the request. For this we’ll need a Management Point fqdn, Management Point Site Code, Package ID, and Package Source Version.

C#:

clr.SiteCode = mySiteCode;
clr.Settings.HostName = myManagementPoint;
clr.LocationRequest.Package.PackageId = packageID;
clr.LocationRequest.Package.Version = packageVersion;

Powershell:

$clr.SiteCode = $ManagementPointSiteCode
$clr.Settings.HostName = $ManagementPoint
$clr.LocationRequest.Package.PackageId = $PackageID
$clr.LocationRequest.Package.Version = $PackageVersion

Now, perhaps one of my favorite things here, is that we can manually set the AD Site code and get a location request as if we were at a different location. This is especially nice if we don’t have access to systems in those specific AD Sites and want to test!

C#:

if (myCustomAdSite != null)
{
   clr.LocationRequest.ContentLocationInfo.ADSite = myCustomAdSite;
}

Powershell:

if ($myCustomAdSite -ne $null)
{
   $clr.LocationRequest.ContentLocationInfo.ADSite = $myCustomAdSite
}

Next we’ll perform a validate on the message (ConfigMgrContentLocationRequest). This call validates that the message we have setup is good. If Validate fails, when the message is sent, it will undoubtedly fail as well. After validate we’ll send the request.

C#:

clr.Validate((IMessageSender)hSender);
cmReply = clr.SendMessage(hSender);

Powershell:

$clr.Validate([Microsoft.ConfigurationManagement.Messaging.Framework.IMessageSender]$httpSender)
$cmReply = $clr.SendMessage($httpSender)

Now we have our returned result! It will be returned as a string but in XML format. We can easily cast this into an XML object and do whatever we wish! Before returning the value, I’ve had some issues casting this due to a trailing  ” ” (Space) at the end of the string. Below is a quick loop that’ll take care of that, should it exist.

C#:

string response = cmReply.Body.Payload.ToString();

while (response[response.Length - 1] != '>')
{
   response = response.TrimEnd(response[response.Length - 1]);
}

Console.WriteLine(response);

Powershell:

$response = $cmReply.Body.Payload.ToString()

while($response[$response.Length -1] -ne '>')
{
   $response = $response.TrimEnd($response[$response.Length -1])
}

 Write-Output $response

Now we’ve got a nice neat Content Location Request! As you see below there’s quite a bit of information here. My primary accomplishment was checking that “SiteLocality” was “LOCAL” and there were LocationRecords.

Config Mgr 2012 R2 SP1:

<ContentLocationReply SchemaVersion="1.00">
   <ContentInfo PackageFlags="1073741825">
      <ContentHashValues/>
   </ContentInfo>
   <Sites>
      <Site>
         <MPSite IISSSLPreferedPort="443" IISPreferedPort="80" SiteLocality="LOCAL" MasterSiteCode="P01" SiteCode="P01"/>
         <LocationRecords>
            <LocationRecord>
               <ADSite Name="MyADSite"/>
               <IPSubnets>
                  <IPSubnet Address="xxx.xxx.xxx.xxx"/>
                  <IPSubnet Address=""/>
               </IPSubnets>
               <Metric Value=""/>
               <Version>8239</Version>
               <Capabilities SchemaVersion="1.0">
                  <Property Name="SSLState" Value="0"/>
               </Capabilities>
               <ServerRemoteName>myServer.Domain.com</ServerRemoteName>
               <DPType>SERVER</DPType>
               <Windows Trust="0"/>
               <Locality>LOCAL</Locality>
            </LocationRecord>
         </LocationRecords>
      </Site>
   </Sites>
   <RelatedContentIDs/>
</ContentLocationReply>

Config Mgr Technical Preview 4 (v1511):

<ContentLocationReply SchemaVersion="1.00">
   <BoundaryGroups BoundaryGroupListRetrieveTime="2015-12-06T22:19:36.400">
      <BoundaryGroup GroupID="16777217"/>
   <BoundaryGroups/>
   <ContentInfo PackageFlags="16777216">
      <ContentHashValues/>
   </ContentInfo>
   <Sites>
      <Site>
         <MPSite IISSSLPreferedPort="443" IISPreferedPort="80" SiteLocality="LOCAL" MasterSiteCode="P01" SiteCode="P01"/>
         <LocationRecords>
            <LocationRecord>
               <URL Signature="http://myServer.Domain.com/SMS_DP_SMSSIG$/XXX#####" Name="http://myServer.Domain.com/SMS_DP_SMSPKG$/XXX#####"/>
               <ADSite Name="MyADSite"/>
               <IPSubnets>
                  <IPSubnet Address="xxx.xxx.xxx.xxx"/>
                  <IPSubnet Address=""/>
               </IPSubnets>
               <Metric Value=""/>
               <Version>8325</Version>
               <Capabilities SchemaVersion="1.0">
                  <Property Name="SSLState" Value="0"/>
               </Capabilities>
               <ServerRemoteName>myServer.Domain.com</ServerRemoteName>
               <DPType>SERVER</DPType>
               <Windows Trust="0"/>
               <Locality>LOCAL</Locality>
            </LocationRecord>
         </LocationRecords>
      </Site>
   </Sites>
   <RelatedContentIDs/>
</ContentLocationReply>

These seem to differ ever so slightly from 2012 R2 (SP1) to v1511. There’s an added “BoundaryGroups” element and “URL Signature” Element as well. However, nothing that would (at least right now) prevent this code from running and working!

 

Full Code

C#:

// Set up the objects
HttpSender hSender = new HttpSender();

ConfigMgrContentLocationRequest clr = new ConfigMgrContentLocationRequest();

ConfigMgrContentLocationReply cmReply = new ConfigMgrContentLocationReply();

// Discover (the try/catch is in case the ADSI COM
// object cannot be loaded (WinPE).
try
{
 clr.Discover();
}
catch
{
   clr.LocationRequest.ContentLocationInfo.IPAddresses.DiscoverIPAddresses();
   clr.LocationRequest.ContentLocationInfo.ADSite.DiscoverADSite();
   clr.LocationRequest.Domain = new ContentLocationDomain() { Name = myDomain };
   clr.LocationRequest.Forest = new ContentLocationForest() { Name = myForest };
}

// Define our SCCM Settings for the message
clr.SiteCode = mySiteCode;
clr.Settings.HostName = myManagementPoint;
clr.LocationRequest.Package.PackageId = packageID;
clr.LocationRequest.Package.Version = packageVersion;

// If we want to "spoof" a request from a different
// AD Site, we can just pass that AD Site in here.
if (myCustomAdSite != null)
{
   clr.LocationRequest.ContentLocationInfo.ADSite = myCustomAdSite;
}

// Validate clr.Validate((IMessageSender)hSender); // Send the message cmReply = clr.SendMessage(hSender);

// Get response
string response = cmReply.Body.Payload.ToString();

while (response[response.Length - 1] != '>')
{
   response = response.TrimEnd(response[response.Length - 1]);
}

Console.WriteLine(response);

Powershell:

# Set up the objects
$httpSender = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Sender.Http.HttpSender

$clr = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ConfigMgrContentLocationRequest

$cmReply = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationReply

# Discover (the try/catch is in case the ADSI COM
# object cannot be loaded (WinPE).
try
{
   $clr.Discover()
}
catch
{
   $clr.LocationRequest.ContentLocationInfo.IPAddresses.DiscoverIPAddresses() $clr.LocationRequest.ContentLocationInfo.ADSite.DiscoverADSite()
   $clr.LocationRequest.Domain = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationDomain
   $clr.LocationRequest.Domain.Name = $myDomain
   $clr.LocationRequest.Forest = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationForest
   $clr.LocationRequest.Forest.Name = $myForest
}

# Define our SCCM Settings for the message
$clr.SiteCode = $ManagementPointSiteCode
$clr.Settings.HostName = $ManagementPoint
$clr.LocationRequest.Package.PackageId = $PackageID
$clr.LocationRequest.Package.Version = $PackageVersion

# If we want to "spoof" a request from a different
# AD Site, we can just pass that AD Site in here.
if ($myCustomAdSite -ne $null)
{
   $clr.LocationRequest.ContentLocationInfo.ADSite = $myCustomAdSite
}

# Validate
$clr.Validate([Microsoft.ConfigurationManagement.Messaging.Framework.IMessageSender]$httpSender)

# Send the message
$cmReply = $clr.SendMessage($httpSender)

# Get response
$response = $cmReply.Body.Payload.ToString()

while($response[$response.Length -1] -ne '>')
{
   $response = $response.TrimEnd($response[$response.Length -1])
}

 Write-Output $response

Enjoi!

Windows 10 with SCCM 2012 R2 (non SP1)

Need to deploy Windows 10 but don’t have the latest Service Pack installed with SCCM? No problem!

Before Windows 10 was released, we were one of many organizations testing Windows 10 within our production environment. With the preview of build 10049, the SCCM Client no longer worked! Looking at the ccmsetup logs an error was indicated when attempted to install the Windows Update Agent. I discovered a solution posted by Jorgen Nilsson (@ccmexec) who simply mentioned to skip this pre-requisite when launching ccmexec.

ccmexec.exe /mp:MyMP.Domain.com /skipprereq:windowsupdateagent30-x64.exe

And Voila! now you can get a client installed. However, as the OSD guy at my company, I was wanting to use OSD to deploy the OS with the full client and not have to worry about failures or any other issues with the Client during the OSD Process. Depending on you deployment scenario (In-Place Upgrade, Bare Metal, Build and Capture) you’ll need to do something slightly different!

Now, if you’ve upgraded to SCCM 2012 R2 SP1 (or SCCM 2012 SP2) this logic is already built-in. Crack open the ccmsetup.cab that’s with your SCCM Client Source Media. Inside is a xml file. Open it up.

SCCM 2012 R2 CU4:

<Item FileHash="D500A5B5945FAFC6A52FB54B7169B62C6C1137E1694184FF2EFF790AA1394ECE" FileName="x64/WindowsUpdateAgent30-x64.exe">
   <Applicability OS="ALL" Platform="X64">
      <Skip>Embedded</Skip>
      <Skip>OS=>=6.1</Skip>
   </Applicability>
   <Discovery Identifier="%windir%\system32\wuapi.dll" Type="File">
      <Property Operator=">=" Name="Version">7.4.7600.226</Property>
   </Discovery>
   <Installation OptionalParams="/quiet /norestart" InstallationType="EXE" Order="3"/>
</Item>

Now if we take a look at SCCM 2012 R2 SP1:

<Item FileHash="D500A5B5945FAFC6A52FB54B7169B62C6C1137E1694184FF2EFF790AA1394ECE" FileName="x64/WindowsUpdateAgent30-x64.exe">
   <Applicability OS="<10" Platform="X64">
      <Skip>Embedded</Skip>
      <Skip>OS=>=6.1</Skip>
   </Applicability>
   <Discovery Identifier="%windir%\system32\wuapi.dll" Type="File">
      <Property Operator=">=" Name="Version">7.4.7600.226</Property>
   </Discovery>
   <Installation OptionalParams="/quiet /norestart" InstallationType="EXE" Order="3"/>
</Item>

Difference being the Applicability tag which with the Service Pack installed (and Microsoft support for Windows 10) we see that the WUAgent is skipped all together, so, skipping it really doesn’t hurt you at all as far as the client goes.

Obviously though, you SHOULD be making plans to get this Service Pack installed, that way you can avoid these workarounds and have full support from Microsoft when deploying!

Build and Capture or Bare Metal

Armed with the information above, for a Task Sequence where you’re not running setup.exe (Build and Capture/Bare Metal/Hardlink) all you need is to add the “/skipprereq” line from above with your “Setup Windows and Config Manager”:

Installation properties: /skipprereq:windowsupdateagent30-x64.exe

SetupWinAndConfigMgr

In-Place Upgrade

This is a bit different since the In-Place Upgrade scenario doesn’t use the Setup Windows and Config Manager Step, however, the same fix applies.

Download the Upgrade Task Sequence found in this TechNet blog by Aaron Czechowski. When you import this into SCCM 2012, a few packages are created, on of which is called “Windows vNext Upgrade Scripts”. If you try to run the Upgrade Windows Task Sequence on a non-service pack SCCM install, you’ll see Windows Setup sit at 100% for a VERY long time, and then potentially rollback or fail out all together. If you look at the logs you’ll see errors pointing at the Windows Update Agent.

Make a copy of this directory and create yourself a revised “Windows vNext Upgrade Scripts”; this way when you do get the Service Pack installed (And you should soon!) you’re just flipping a package reference! The Upgrade Scripts are referenced by Windows Setup.exe. Look at the “Upgrade Windows” Step of this task sequence

Setup.exe /Auto:Upgrade /Quiet /NoReboot /DynamicUpdate Disable /PostOobe %SystemDrive%\_vNextUpgrade /PostRollback %SystemDrive%\_vNextUpgrade

What’s happening is when you specify the /PostOobe flag, its looking for a file “SetupComplete.cmd”; same logic applies for /PostRollback, which is looking for SetupRollback.cmd. (Feel free to have fun with that information!)

Now, cracking open SetupComplete.cmd, its essentially launching the powershell script SetupComplete.ps1. Edit SetupComplete.ps1 and look for this line:

$process = Start-Process $env:WinDir\ccmsetup\ccmsetup.exe -ArgumentList "/remediate:client" -Wait -NoNewWindow -PassThru

Modify it by injecting the skipprereq from above and you get this:

$process = Start-Process $env:WinDir\ccmsetup\ccmsetup.exe -ArgumentList "/remediate:client /skipprereq:windowsupdateagent30-x64.exe" -Wait -NoNewWindow -PassThru

Beautiful right? Well, almost. If you’ll be using this same package for 32-bit machines, you’ll need to exclude the x86 WUAgent, not the x64 one. Quick WMI call and you’re there:

if ((Get-WmiObject Win32_OperatingSystem).OsArchitecture -eq "64-bit")
{
   $process = Start-Process $env:WinDir\ccmsetup\ccmsetup.exe -ArgumentList "/remediate:client /skipprereq:windowsupdateagent30-x64.exe" -Wait -NoNewWindow -PassThru
}
else 
{
   $process = Start-Process $env:WinDir\ccmsetup\ccmsetup.exe -ArgumentList "/remediate:client /skipprereq:windowsupdateagent30-x86.exe" -Wait -NoNewWindow -PassThru
}

Quick fix to help you get Windows 10 deployed while waiting for that Service-Pack to finally be installed!

 

Enjoi!

Installing System Center Endpoint Protection on Windows 10

July 2015: Now that Windows 10 is RTM, is strongly recommend upgrading your SCCM infrastructure to the appropriate service pack. SCCM 2012 SP2 (or SCCM 2012 R2 SP1) provides native support for Windows 10. If you experience issues past the Service Pack upgrade, please consult your Microsoft Support contacts.

With Windows 10, you do not need to deploy SCEP as SCCM Can now manage Windows Defender out of the box: https://technet.microsoft.com/en-us/library/hh508770.aspx

Leaving this workaround up now that a supported process is in place, I fear that some may try to implement this non-support workaround in a production environment.