Wednesday, February 22, 2017

Windows 10 In-Place Upgrade Assessment Error Handling

As I progress to making Windows 10 available to end users I needed to polish out the error handling. While I am using the Upgrade Readiness tool that Microsoft provides to target successful systems, I am still performing one final check in the Task Sequence by having the Task Sequence do an assessment before continuing and capturing those results.

You just insert the Upgrade Operating System step and select the box 'Perform Windows Setup compatibility scan without starting upgrade' at minimum. I have also selected the other chekboxes below it such as 'Ignore any dismissible compatibility messages' checkbox. If unchecked, this will for example, trigger a failure for an incompatible driver that gets removed anyway. Useful if you have some weird driver for a microscope type device (I do!) but not useful if you accept the removal that the upgrade does. For those types of endpoints the Readiness tool has identified them.

I also enabled the last two check boxes. These will cause the eval step to pull the latest eval info from Microsoft instead of using what data shipped on the ISO. There may be some proxy concerns here.

Here is what that part of the Task Sequence looks like:

For initial roll out to IT, I am using Niall C. Brady's dialog Powershell Script so I wont cover that part here. I am working on a template script to pull HTML edited file to be more polished for an end user when I get to that point but Niall's script works great.

The 'Upgrade Assessment' step outputs to the read-only variable _SMSTSOSUpgradeActionReturnCode. SETUP.EXE actually outputs in hex whereas the variable is in decimal. Looking at Microsofts blog post these are the major exit codes. I converted the hex to decimal with a converter so I work in the same format ConfigMgr is.

  • No issues found:  0xC1900210 (3247440400)
  • Compatibility issues found (hard block):  0xC1900208 (3247440392)
  • Migration choice (auto upgrade) not available (probably the wrong SKU or architecture)· 0xC1900204 (3247440388)
  • Does not meet system requirements for Windows 10: 0xC1900200  (3247440384)
  • Insufficient free disk space: 0xC190020E (3247440398)

The 'Assessment Errors Detected' group has a condition of _SMSTSOSUpgradeActionReturnCode ≠ 3247440400 whereas the 'Upgrade the Operating System' group has _SMSTSOSUpgradeActionReturnCode = 3247440400.

Under the 'Assessment Errors Detected' Group, each of the 4 sub groups matches a code in _SMSTSOSUpgradeActionReturnCode. For example, the 'Compatibility Issues found (hard block)' group has a condition for _SMSTSOSUpgradeActionReturnCode = 3247440392.

Then each error code displays a custom message and errors the Task Sequence out. For the  'Compatibility Issues found (hard block)' error it shows

The Upgrade Assessment detected an error which is preventing a successful upgrade and it must be mitigated first. There is an application or driver that must be removed first. This is generally due to an old version of Sophos Safeguard present. Please contact the IT Service Desk for assistance.

Again, these groups are just a final sanity check, While the Readiness tool identifies bullet 2 impacted systems, ConfigMgr collections are identifying the last two bullets for example.


Monday, February 13, 2017

Replace Edge with Internet Explorer on Windows 10 (1607) via Task Sequence (Part II)

My most popular post is how I replaced Edge with IE in Windows 10. Since then, Microsoft made it a substantially easier in version 1607 to do this. IE is still a little wonky though as you'll see. I was able to take it down to two steps. Just the 'customize Start' and 'Copy IE Shortcut to Default Profile' steps. All the highlighted steps were removed and not needed anymore

I'll cover below as it could just be one step depending on your environment. My previous post is still worth a read as I wont rehash parts of it here and it covers some of the mechanics that still apply in 1607.

So what changed? Microsoft expanded how you can modify via the Export-StartLayout and Import-StartLayout PowerShell cmdlets. They have a good article about it here.

Task Bar

For the startbar modification I took the sample from the Technet article and added in the paths to the shortcuts so they get pinned (bolded below). There are some places you will NOT be able to point to such as %PROGRAMDATA% but know that %ALLUSERSPROFILE% points to the same location by default.

Additionally, you can append or replace based on the PinListPlacement element.

 <?xml version="1.0" encoding="utf-8"?>  
  <CustomTaskbarLayoutCollection PinListPlacement="Replace">  
     <taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\Accessories\Internet Explorer.lnk" />  
     <taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\File Explorer.lnk" />  
     <taskbar:DesktopApp DesktopApplicationLinkPath="%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs\Outlook 2016.lnk" />  

As the Technet article states, if you are unsure of the path you can pin it to the start menu and export it to get the path then put that under the taskbar section.

Taskbar is done and tested and was the whole fight in my last article. Way better right? Now onto the Start Menu.

Start Menu

For the Start Menu I setup a clean machine and organized it how I wanted and then exported it via the PowerShell cmdlet.

 Export-StartLayout \\server\path\to\LayoutModification.XML  

In my task sequence I then imported each XML but only the Taskbar was changed. Turns out you can only import ONE XML that manipulates the default desktop. So I had to merge them into one file. I split them thinking we may have a need to modify one over the other and since the Taskbar was imported second it trumped the Start Menu XML.

Heres the bonkers part. The IE shortcut for the Start Menu. This is how I learned that it does not honor %PROGRAMDATA% path to items. You are stuck in the majority to %ALLUSERSPROFILE% and %APPDATA% variable paths.

for my taskbar I am using "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Accessories\Internet Explorer.lnk" so I referenced that as well for the Start Menu section. Nope, does not work. %APPDATA% points to your roaming profile. My suspicion is that the Start menu is created a little earlier in the initial login process so it shows up blank due to the legacy profile paths are not created yet.

According to this MSDN article that f you are pointing to a third-party Classic Windows application, you must put the .lnk file in a legacy Start Menu directory before first boot. For example, "%APPDATA%\Microsoft\Windows\Start Menu\Programs\" or the all users profile located at"%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs\". IE falls under this.

With that said though, I am able to point to the IE executable under %PROGRAMFILES%  and use this in the XML

 <start:DesktopApplicationTile Size="1x1" Column="0" Row="0" DesktopApplicationLinkPath="%PROGRAMFILES%\Internet Explorer\iexplore.exe" />  

It works, but ONLY for the first user to login. I think Windows modifies the imported XML to update it to %APPDATA%, which if you do an export right after login it reflects this.

 <start:DesktopApplicationTile Size="1x1" Column="0" Row="0" DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\Accessories\Internet Explorer.lnk" />  

So on subsequent users logging in they do not get this icon. The first user is fine through all their logins. As many of my firms machines are used by multiple users, I decided to just copy the IE shortcut and point to that in my XML. As all the office shortcuts are put under "%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs" I just put the IE shortcut there. If you do not want to pin IE to the Start Menu then this is not needed. I have it pinned to both the Task Bar and Start Menu. For the Task Bar I could change to this precopied one or leave as is in the users profile.

Task Sequence

For the task sequence I just have two steps. The 'Customize Start' step is a PowerShell step that calls 'Configure-StartMenu.ps1' that just contains

 Import-StartLayout -LayoutPath .\LayoutModification.xml -MountPath $env:SystemDrive\  

The 'Copy IE shortcut to default profile' step simply copies the file from the package to the default users start.

 xcopy "Internet Explorer.lnk" "%ALLUSERSPROFILE%\Microsoft\Windows\Start Menu\Programs" /E /C /I /H /Y  

I was done! Now that i know about the path issue for IE, the default Start can be updated within a few minutes.

Upon doing some final bare metal VM deployments, I noticed that a Start Menu shortcut for FireFox was missing. Who do we use during deployment? BUILTIN\SYSTEM. This makes me think further that the custom Start Menu is generated at login of ANY user after importation but not applied to an existing user. So by moving the 'Customize Start' step towards the end of the Task Sequence, this was resolved as it ran AFTER applications that are pinned to the Start Menu are installed, like FireFox.


Thursday, February 9, 2017

Update OneDrive Next Gen Sync agent during Windows 10 Deployment

For my environment, the OneDrive Next gen sync agent is installed by Office 2016 Click-to-Run as it still Windows 7 for the majority. For the Windows 10 systems however its included in the OS and even with 1607 is now out of date. Instead of letting it update after deployment and presenting dialogs to the user, I thought I would update it via the deployment Task Sequence so its current right out of the box.

I chose to handle this via a simple BAT file thats really just 3 lines thats needed. Windows 10 keeps the OneDriveSetup.EXE  in %WINDIR%\SysWOW64 folder on 64-Bit systems and %WINDIR%\SYSTEM32 on 32-Bit. It is a 32-bit application so the File System Redirector handles how its presented. So first we need to find the file. Note that the Office install puts it under Program Files(x86)\OneDrive.

 IF EXIST %SYSTEMROOT%\System32\OneDriveSetup.exe SET ONEDRIVEPATH=%SYSTMEROOT%\System32\OneDriveSetup.exe  
 IF EXIST %SYSTEMROOT%\SysWOW64\OneDriveSetup.exe SET ONEDRIVEPATH=%SYSTEMROOT%\SysWow64\OneDriveSetup.exe  

Since the file is owned by BUILTIN\TrustedInstaller we have to take ownership so SCCM is able to replace it.

 %SYSTEMROOT%\system32\takeown.exe /f %ONEDRIVEPATH%  

Now that we own it, we can modify permissions. Since its ran during the TS we are running as BUILTIN\System so lets give that account Full rights to the file. This allows us to delete and replace it.

 %SYSTEMROOT%\system32\icacls.exe %ONEDRIVEPATH% /Grant System:(F)  

Then simply copy the newer one. If your doing manually outside of a TS step you should use '%~dp0' variable.

 Copy OneDriveSetup.exe %ONEDRIVEPATH%  

The entire script is below if you want to just use it as is.

 :: from  
 :: written by Kevin Fason  
 :: This will take ownership of OneDriveSetup.exe for Windows 10 so we can update it  
 :: Version 1.0  
 ::  Initial Release  
 :: February 6, 2017  
 :: Locate it due to system32/Syswow64 switching per what bitlevel this scrip runs in  
 IF EXIST %SYSTEMROOT%\System32\OneDriveSetup.exe SET ONEDRIVEPATH=%SYSTMEROOT%\System32\OneDriveSetup.exe  
 IF EXIST %SYSTEMROOT%\SysWOW64\OneDriveSetup.exe SET ONEDRIVEPATH=%SYSTEMROOT%\SysWow64\OneDriveSetup.exe  
 :: Make changes  
 :: Take Ownership  
 %SYSTEMROOT%\system32\takeown.exe /f %ONEDRIVEPATH%  
 :: Give BUILTIN\System Full rights  
 %SYSTEMROOT%\system32\icacls.exe %ONEDRIVEPATH% /Grant System:(F)  
 :: Replace the old one  
 Copy OneDriveSetup.exe %ONEDRIVEPATH%  

There are many other ways to handle something like this. Since this BAT is called from a wrapper there is zero checks and balances as the wrapper and ConfigMgr application handle that.  I chose an if then but you can use a goto statement as well for each bitlevel location. Doing it via Powershell is possible also however a BAT is alot simpler IMO. My co-worker Jason provided this as an example while still using built in executibles for the ownership and ACL work.

 $Path32 = $ENV:SYSTEMROOT + '\system32\OneDriveSetup.exe'  
 $Path64 = $ENV:SYSTEMROOT + '\syswow64\OneDriveSetup.exe'  
   [Environment]::SetEnvironmentVariable("ONEDRIVEPATH", $Path32, "Machine")  
   & c:/Windows/system32/takeown.exe /f $Path32  
   & c:/Windows/system32/icacls.exe $Path32 /Grant ("System" + ':F')  
 Copy-item ./OneDriveSetup.exe $Path32 -force   
   Write '32'  
   [Environment]::SetEnvironmentVariable("ONEDRIVEPATH", $Path64, "Machine")  
   & c:/Windows/system32/takeown.exe /f $Path64  
   & c:/Windows/system32/icacls.exe $Path64 /Grant ("System" + ':F')  
  Copy-item ./OneDriveSetup.exe $Path64 -force   
   Write '64'