Workaround script to clean up SCCM 1610 orphaned cache

SCCM 1610 at launch had a bug that caused agent upgrades to forget about cached content. Cached data stays behind until you clean it up manually, not cool for small SSDs. More here https://support.microsoft.com/en-us/kb/3214042

So I wrote a small script to roll out with compliance and remove stale data.

Seems to work but test before use. See comments for PowerShell 2.0 fix.

$CCMCache = (New-Object -ComObject "UIResource.UIResourceMgr").GetCacheInfo().Location
#For some reason it doesn't properly directly select required attribute for returned multi-instance object so I have to loop it. Some strange COM-DotNet interop problem?
$ValidCachedFolders = (New-Object -ComObject "UIResource.UIResourceMgr").GetCacheInfo().GetCacheElements() | ForEach-Object {$_.Location}
$AllCachedFolders = (Get-ChildItem -Path $CCMCache -Directory).FullName

ForEach ($CachedFolder in $AllCachedFolders) {
    If ($ValidCachedFolders -notcontains $CachedFolder) {
        Remove-Item -Path $CachedFolder -Force -Recurse
    }
}

Script to modify SCCM client cache ACL for Peer Cache

SCCM 1610 now supports inter-node content sharing without BranchCache or 3rd party tools. Annoying part is that you have to modify client cache ACL. I threw together some quick-n-dirty bits in a few minutes and it didn’t blow in my face just yet. I rolled it out with a compliance baseline to some pilot systems and it seems to work.
Caution is advised as I didn’t test it fully yet (or if Peer Cache actually works properly). It just adds required ACE for your SCCM network access account.

#SCCM Network Access account. I think it's not possible to query it from client
$NetworkUserAccount = New-Object System.Security.Principal.NTAccount("DOMAIN\User")
#SCCM Cache path from WMI. It's pretty much the same always but just in case...
$CCMCache = (New-Object -ComObject "UIResource.UIResourceMgr").GetCacheInfo().Location

#Enums for NTFS ACLs, static stuff. Could do better but stringbased cast works fine
$ACLFileSystemRights = [System.Security.AccessControl.FileSystemRights]::FullControl
$ACLAccessControlType = [System.Security.AccessControl.AccessControlType]::Allow 
$ACLInheritanceFlags = [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit"
$ACLPropagationFlags = [System.Security.AccessControl.PropagationFlags]::InheritOnly

#If cache folder doesn't exist, quit with error
If (!(Get-Item -Path $CCMCache)) {
    Exit 1
}

#Current ACL
$ACL = Get-Acl -Path $CCMCache

#Check if ACL already has required entry. If it has, quit cleanly
If ($ACL.Access | Where-Object -FilterScript {
    #Specific checks
    $_.FileSystemRights -eq $ACLFileSystemRights -and 
    $_.AccessControlType -eq $ACLAccessControlType -and
    $_.IdentityReference -eq $NetworkUserAccount -and
    $_.InheritanceFlags -eq $ACLInheritanceFlags -and
    $_.PropagationFlags -eq $ACLPropagationFlags
    }
) {
    #ACL entry exists
    Exit 0
} Else {
    #Modify ACL
    $ACE = New-Object System.Security.AccessControl.FileSystemAccessRule ($NetworkUserAccount, $ACLFileSystemRights, $ACLInheritanceFlags, $ACLPropagationFlags, $ACLAccessControlType) 
    $ACL.AddAccessRule($ACE)
    Set-Acl -Path $CCMCache -AclObject $ACL
}

WinPE manage-bde –protectors –disable C: unexpectedly enables encryption

Final update – my BIOS configuration script had a command to temporarily disable BitLocker in case configuration was applied from already deployed OS. Good old manage-bde –protectors –disable C:

However this command unexpectedly applied BitLocker to FAT32 boot volume. When querying status with manage-bde -status there is no encryption. However volume is actually encrypted. Booting to WinPE on next start would clear encryption so it only showed up when using Linux live media. Duh!

Why would it do that? Don’t know. In the end HP BIOS boots just fine and does not require ESP partition. I edited title to reflect on the actual issue.

Hold your horses! All information below is irrelevant as HP desktop BIOS seems to have a bug. It will not properly enumerate UEFI boot drives after mode switch. It may boot sometimes but not consistently. Currently only workaround is to boot to PXE after mode switch and restart TS.

So I was looking at this great guide on conversion from BIOS to UEFI boot in SCCM TS.

However my BIOS/UEFI configuration is more locked down and HP professional desktops flat out refuse to boot from plain FAT32 partitions with some options set. I’m guessing it’s because of Removable Media Boot: Disable. But still I needed to work around that. After some tinkering I discovered that boot worked fine if partition was set as EFI boot partition. However this caused WinPE to not mount it at boot. No mount, no task sequence data, fail.

So I created 2 partitions, first for EFI boot, second for WinPE. Then I configured BCD to point to second partition and set first as EFI boot partition. Boom, it works!

Notes:

  • As always, could be more efficient but good enough…
  • My configuration is a bit different. I have both x86 and amd64 WinPE data in one package (in subfolders) to support both 32bit and 64bit UEFI implementations in one package and I select relevant boot set with %PROCESSOR_ARCHITECTURE% variable. Package download size is bigger but that’s not an issue for me. This also implies that PXE WinPE image must be the same as target architecture.
  • In “Format and Partition Disk” step create 2 Primary partitions. First must be smaller than the second one (for example 2GB and 4GB). This is necessary as TS data is stored on the larger partition and EFI partition will not be mounted on next boot. Set first partition variable to EfiDrive and second to BootDrive
  • Call WinPE deployment script as
    
    
    
    copy.cmd %EfiDrive% %BootDrive%

Modified copy.cmd



@ECHO OFF
set efidrive=%1
::S:
set bootdrive=%2
::C:
XCOPY %~dp0%PROCESSOR_ARCHITECTURE%\* /s /e /h %bootdrive%\

::https://technet.microsoft.com/en-us/library/hh265131%28v=ws.10%29.aspx

xcopy %bootdrive%\EFI\* %efidrive%\EFI\* /cherkyfs
copy %bootdrive%\windows\boot\EFI\*.efi %efidrive%\EFI\Microsoft\Boot\*
del %efidrive%\EFI\Microsoft\Boot\BCD /f

bcdedit -createstore %efidrive%\EFI\Microsoft\Boot\BCD

bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -create {bootmgr} /d "Boot Manager"
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -create {globalsettings} /d "globalsettings"
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -create {dbgsettings} /d "debugsettings"
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -create {ramdiskoptions} /d "ramdiskoptions"
for /f "Tokens=3" %%A in ('bcdedit /store %efidrive%\EFI\Microsoft\Boot\BCD /create /application osloader') do set PEStoreGuid=%%A

bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD /default %PEStoreGuid%

bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {bootmgr} device partition=%efidrive%
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {bootmgr} path \EFI\Microsoft\Boot\bootmgfw.efi
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {bootmgr} locale en-us
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {bootmgr} timeout 10

bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {Default} device ramdisk=[%bootdrive%]\sources\boot.wim,{ramdiskoptions}
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {Default} path \windows\system32\winload.efi
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {Default} osdevice ramdisk=[%bootdrive%]\sources\boot.wim,{ramdiskoptions} 
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {Default} systemroot \windows
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {Default} winpe yes
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {Default} nx optin
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -set {Default} detecthal yes
bcdedit -store %efidrive%\EFI\Microsoft\Boot\BCD -displayorder {Default} -addfirst

diskpart /s "%~dp0diskpartefiboot.txt"


diskpartefiboot.txt



select disk 0
select partition 1
set id=c12a7328-f81f-11d2-ba4b-00a0c93ec93b


Voilà! It boots!

…occasionally on some systems fastfat driver doesn’t load. It’s load type is 3 – manual (ondemand). Partitions are shown as RAW and TS will fail as data cannot be loaded. Investigating.

Superseding dependencies in System Center Configuration Manager‏

Yeah, it’s difficult, error-prone and somewhat buggy.

Imagine following scenario:

  • Library application X v1
  • Main application Y, depends on X
  • You need to upgrade X v1 to v2 by first uninstalling old version and then installing new version

The only way I’ve seen this work is deleting dependency, deploying X upgrade semi-manually and then setting dependency to v2. Any other attempt will get you “Rule is in conflict with other rules” in deployment monitoring as agent will refuse to remove v1.

The second scenario:

  • Library application Z v1
  • Main application Q, depends on Z
  • You need to upgrade Z to v2, no uninstall is necessary

This was explicitly added in 2012 R2 SP1 that made this scenario possible if v2 superseded v1. In real life I’ve found this very unreliable. The worst I’ve seen was agent gobbling up GBs of RAM grinding systems to a halt. Application detection got stuck in loop and leaking memory as some old applications were set to depend on v1 and some newer on v2. In better cases – good old “Rule is in conflict with other rules”.

There used to be a workaround. Add both Z v1 and Z v2 to the same dependency group but clear Install Automatically flag on v1. This seems to have stopped working in 1602 or 1606 as client will stop dependency processing if v1 is not found. I only tested this very briefly in new builds so do not trust me on that. Might be a bug or maybe original behavior in 2012 R2 was buggy…

This makes you wish for something like MDT application bundles. Group Z versions into bundle and create dependancy on bundle.

Generally application model is great but with a lot of annoying gotchas. Or maybe it’s just me…

Leave a note if comments if you’ve found a better way.

Deleting System Center Configuration Manager application will not delete supersedence relationship‏

Imagine a scenario where you have following applications is SCCM:

  • Application X v1.0
  • Application X v1.1 – supersedes v1.0
  • Application X v1.2 – supersedes v1.1, deployed to clients

At one point you might want to delete v1.0 from SCCM. At this point you must keep in mind that supersedence data will not be updated for v1.1. v1.1 will still contain broken supersedence information that will break deployment for even v1.2 as client can no longer build supersedence chain (v1.1 references nonexistent application). You must manually remove supersedence information from v1.1.

Observed in 1602 and 1606. I’ve thought about scripting this validation-remediation but SCCM PowerShell is quite cryptic past very basic operations (WQL or sparsely documented .Net classes for most things). I guess a deep dive into SCCM SDK is in order someday.

Funny thing is that this seems to be only relation that is not enforced. And no, I haven’t contacted MS support about this as it’s not that important for me to burn a support ticket.