Tag Archives: MECM

PowerShell | Working with Trusted Platform Modules (TPM) via WMI during OSD

Because the legacy WMI PowerShell cmdlets (e.g. Get-WmiObject) are eventually going to be deprecated, I always try to use the newer CIM-based PowerShell cmdlets (e.g. Get-CimInstance) wherever possible.

This can be a bit confusing sometimes though and it can appear that the new CIM cmdlets have less functionality than their older WMI counterparts. This isn’t the case as I explain later on in the blog post.

This perceived difference is especially true when working with TPM chips on devices. Below is an example of running a query against the ‘Win32_Tpm‘ class in WMI using both the old and new cmdlets.

The legacy ‘Get-WmiObject‘ cmdlet shows ‘70‘ Properties/Methods while the newer ‘Get-CimInstance‘ cmdlet shows only ‘20‘.

(Get-WmiObject -Namespace 'root/cimv2/Security/MicrosoftTpm' -Class 'Win32_Tpm' | Get-Member).Count
70

(Get-CimInstance -Namespace 'root/cimv2/Security/MicrosoftTpm' -Class 'Win32_Tpm' | Get-Member).Count
20

One WMI Method that I use regularly with OSD is the ‘SetPhysicalPresenceRequest‘ Method to configure a TPM to be cleared, activated and enabled. If you use the value of ‘14‘ for the request then you need to configure the firmware/BIOS to not require Physical Presence otherwise you’ll need someone to physically press a key to confirm the TPM clear is allowed.

If you can’t configure the firmware/BIOS to disable requiring physical presence confirmation then you can use the request value of ‘10‘ which won’t ask for physical confirmation but is slightly less effective. Using ‘10‘ should still mean your TPM is ready to be accessed by encryption-related commands later on in the Task Sequence though.

To use this command in a MCM Task Sequence I would historically use a ‘Run Command Line‘ task to run the following PowerShell command:

powershell.exe -ExecutionPolicy bypass -Command "(Get-WmiObject -Namespace "root\CIMV2\Security\MicrosoftTpm" -Class Win32_TPM).SetPhysicalPresenceRequest(14)"

Given my previous statement that I want to use the more modern ‘Get-CimInstance‘ cmdlets I looked into how this could be done with the newer cmdlets so that if or when the legacy WmiObject cmdlets are no longer available in Windows, my Task Sequence commands will continue to run successfully without any changes being needed.

By running ‘Get-WmiObject‘ we can see that ‘SetPhysicalPresenceRequest‘ is listed as an available Method for us to use:

Get-WmiObject -Namespace 'root/cimv2/Security/MicrosoftTpm' -Class 'Win32_Tpm' | Get-Member -MemberType Method


   TypeName: System.Management.ManagementObject#root\cimv2\Security\MicrosoftTpm\Win32_Tpm

Name                                  MemberType Definition
----                                  ---------- ----------
AddBlockedCommand                     Method     System.Management.ManagementBaseObject AddBlockedCommand(System.UIn...
ChangeOwnerAuth                       Method     System.Management.ManagementBaseObject ChangeOwnerAuth(System.Strin...
Clear                                 Method     System.Management.ManagementBaseObject Clear(System.String OwnerAuth)
ConvertToOwnerAuth                    Method     System.Management.ManagementBaseObject ConvertToOwnerAuth(System.St...
CreateEndorsementKeyPair              Method     System.Management.ManagementBaseObject CreateEndorsementKeyPair()
Disable                               Method     System.Management.ManagementBaseObject Disable(System.String OwnerA...
DisableAutoProvisioning               Method     System.Management.ManagementBaseObject DisableAutoProvisioning(Syst...
Enable                                Method     System.Management.ManagementBaseObject Enable(System.String OwnerAuth)
EnableAutoProvisioning                Method     System.Management.ManagementBaseObject EnableAutoProvisioning()
GetCapLockoutInfo                     Method     System.Management.ManagementBaseObject GetCapLockoutInfo()
GetDictionaryAttackParameters         Method     System.Management.ManagementBaseObject GetDictionaryAttackParameters()
GetOwnerAuth                          Method     System.Management.ManagementBaseObject GetOwnerAuth()
GetOwnerAuthForEscrow                 Method     System.Management.ManagementBaseObject GetOwnerAuthForEscrow()
GetOwnerAuthStatus                    Method     System.Management.ManagementBaseObject GetOwnerAuthStatus()
GetPhysicalPresenceConfirmationStatus Method     System.Management.ManagementBaseObject GetPhysicalPresenceConfirmat...
GetPhysicalPresenceRequest            Method     System.Management.ManagementBaseObject GetPhysicalPresenceRequest()
GetPhysicalPresenceResponse           Method     System.Management.ManagementBaseObject GetPhysicalPresenceResponse()
GetPhysicalPresenceTransition         Method     System.Management.ManagementBaseObject GetPhysicalPresenceTransition()
GetSrkADThumbprint                    Method     System.Management.ManagementBaseObject GetSrkADThumbprint(System.By...
GetSrkPublicKeyModulus                Method     System.Management.ManagementBaseObject GetSrkPublicKeyModulus()
GetTcgLog                             Method     System.Management.ManagementBaseObject GetTcgLog()
ImportOwnerAuth                       Method     System.Management.ManagementBaseObject ImportOwnerAuth(System.Strin...
IsActivated                           Method     System.Management.ManagementBaseObject IsActivated()
IsAutoProvisioningEnabled             Method     System.Management.ManagementBaseObject IsAutoProvisioningEnabled()
IsCommandBlocked                      Method     System.Management.ManagementBaseObject IsCommandBlocked(System.UInt...
IsCommandPresent                      Method     System.Management.ManagementBaseObject IsCommandPresent(System.UInt...
IsEnabled                             Method     System.Management.ManagementBaseObject IsEnabled()
IsEndorsementKeyPairPresent           Method     System.Management.ManagementBaseObject IsEndorsementKeyPairPresent()
IsFIPS                                Method     System.Management.ManagementBaseObject IsFIPS()
IsKeyAttestationCapable               Method     System.Management.ManagementBaseObject IsKeyAttestationCapable()
IsLockedOut                           Method     System.Management.ManagementBaseObject IsLockedOut()
IsOwned                               Method     System.Management.ManagementBaseObject IsOwned()
IsOwnerClearDisabled                  Method     System.Management.ManagementBaseObject IsOwnerClearDisabled()
IsOwnershipAllowed                    Method     System.Management.ManagementBaseObject IsOwnershipAllowed()
IsPhysicalClearDisabled               Method     System.Management.ManagementBaseObject IsPhysicalClearDisabled()
IsPhysicalPresenceHardwareEnabled     Method     System.Management.ManagementBaseObject IsPhysicalPresenceHardwareEn...
IsReady                               Method     System.Management.ManagementBaseObject IsReady()
IsReadyInformation                    Method     System.Management.ManagementBaseObject IsReadyInformation()
IsSrkAuthCompatible                   Method     System.Management.ManagementBaseObject IsSrkAuthCompatible()
OwnerAuthEscrowed                     Method     System.Management.ManagementBaseObject OwnerAuthEscrowed(System.Str...
Provision                             Method     System.Management.ManagementBaseObject Provision(System.Boolean For...
RemoveBlockedCommand                  Method     System.Management.ManagementBaseObject RemoveBlockedCommand(System....
ResetAuthLockOut                      Method     System.Management.ManagementBaseObject ResetAuthLockOut(System.Stri...
ResetSrkAuth                          Method     System.Management.ManagementBaseObject ResetSrkAuth(System.String O...
SelfTest                              Method     System.Management.ManagementBaseObject SelfTest()
SetPhysicalPresenceRequest            Method     System.Management.ManagementBaseObject SetPhysicalPresenceRequest(S...
TakeOwnership                         Method     System.Management.ManagementBaseObject TakeOwnership(System.String ...

Running the same command with the ‘Get-CimInstance‘ cmdlet brings back significantly fewer Methods and most importantly ‘SetPhysicalPresenceRequest‘ is missing from the list of Methods!!!!

Get-CimInstance -Namespace 'root/cimv2/Security/MicrosoftTpm' -ClassName 'Win32_Tpm' | Get-Member -MemberType Method


   TypeName: Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Security/MicrosoftTpm/Win32_Tpm

Name                      MemberType Definition
----                      ---------- ----------
Clone                     Method     System.Object ICloneable.Clone()
Dispose                   Method     void Dispose(), void IDisposable.Dispose()
Equals                    Method     bool Equals(System.Object obj)
GetCimSessionComputerName Method     string GetCimSessionComputerName()
GetCimSessionInstanceId   Method     guid GetCimSessionInstanceId()
GetHashCode               Method     int GetHashCode()
GetObjectData             Method     void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System....
GetType                   Method     type GetType()
ToString                  Method     string ToString()

“Where’s my bloody Method?” I asked whilst preparing myself to overcome OCD and continue using the legacy command…

However, under the covers the ‘SetPhysicalPresenceRequest‘ method still exists in WMI but we just can’t see it as easily using ‘Get-CimInstance‘. In order to view these hidden Methods we need to run a slightly different PowerShell command as per below:

(Get-CimInstance -Namespace root/cimv2/Security/MicrosoftTpm -ClassName Win32_Tpm).CimClass.CimClassMethods

Name                                  ReturnType Parameters                                                         Qua
                                                                                                                    lif
                                                                                                                    ier
                                                                                                                    s
----                                  ---------- ----------                                                         ---
IsEnabled                                 UInt32 {IsEnabled}                                                        {De
IsOwned                                   UInt32 {IsOwned}                                                          {De
IsActivated                               UInt32 {IsActivated}                                                      {De
IsPhysicalClearDisabled                   UInt32 {IsPhysicalClearDisabled}                                          {De
IsOwnerClearDisabled                      UInt32 {IsOwnerClearDisabled}                                             {De
IsPhysicalPresenceHardwareEnabled         UInt32 {IsPhysicalPresenceHardwareEnabled}                                {De
IsOwnershipAllowed                        UInt32 {IsOwnershipAllowed}                                               {De
IsCommandPresent                          UInt32 {CommandOrdinal, IsCommandPresent}                                 {De
Enable                                    UInt32 {OwnerAuth}                                                        {De
Disable                                   UInt32 {OwnerAuth}                                                        {De
IsEndorsementKeyPairPresent               UInt32 {IsEndorsementKeyPairPresent}                                      {De
CreateEndorsementKeyPair                  UInt32 {}                                                                 {De
TakeOwnership                             UInt32 {OwnerAuth}                                                        {De
Clear                                     UInt32 {OwnerAuth}                                                        {De
IsSrkAuthCompatible                       UInt32 {IsSrkAuthCompatible}                                              {De
ResetSrkAuth                              UInt32 {OwnerAuth}                                                        {De
ChangeOwnerAuth                           UInt32 {NewOwnerAuth, OldOwnerAuth}                                       {De
SelfTest                                  UInt32 {SelfTestResult}                                                   {De
ConvertToOwnerAuth                        UInt32 {OwnerPassPhrase, OwnerAuth}                                       {De
SetPhysicalPresenceRequest                UInt32 {Request, RequestParameter}                                        {De
GetPhysicalPresenceRequest                UInt32 {Request}                                                          {De
GetPhysicalPresenceTransition             UInt32 {Transition}                                                       {De
GetPhysicalPresenceResponse               UInt32 {Request, Response}                                                {De
AddBlockedCommand                         UInt32 {CommandOrdinal}                                                   {De
RemoveBlockedCommand                      UInt32 {CommandOrdinal}                                                   {De
IsCommandBlocked                          UInt32 {CommandOrdinal, IsCommandBlocked}                                 {De
ResetAuthLockOut                          UInt32 {OwnerAuth}                                                        {De
IsReady                                   UInt32 {IsReady}                                                          {De
IsReadyInformation                        UInt32 {Information, IsReady}                                             {De
IsAutoProvisioningEnabled                 UInt32 {IsAutoProvisioningEnabled}                                        {De
EnableAutoProvisioning                    UInt32 {}                                                                 {De
DisableAutoProvisioning                   UInt32 {OnlyForNextBoot}                                                  {De
GetOwnerAuth                              UInt32 {OwnerAuth}                                                        {De
Provision                                 UInt32 {ForceClear_Allowed, PhysicalPresencePrompts_Allowed, Information} {De
ImportOwnerAuth                           UInt32 {OwnerAuth}                                                        {De
GetPhysicalPresenceConfirmationStatus     UInt32 {Operation, ConfirmationStatus}                                    {De
GetSrkPublicKeyModulus                    UInt32 {SrkPublicKeyModulus}                                              {De
GetSrkADThumbprint                        UInt32 {SrkPublicKeyModulus, SrkADThumbprint}                             {De
GetTcgLog                                 UInt32 {TcgLog}                                                           {De
IsKeyAttestationCapable                   UInt32 {TestResult}                                                       {De
GetOwnerAuthForEscrow                     UInt32 {OwnerAuth, OwnerAuthStatus}                                       {De
OwnerAuthEscrowed                         UInt32 {OwnerAuth}                                                        {De
GetOwnerAuthStatus                        UInt32 {OwnerAuthStatus}                                                  {De
IsFIPS                                    UInt32 {IsFIPS}                                                           {De
GetDictionaryAttackParameters             UInt32 {LockoutRecovery, MaxTries, RecoveryTime}                          {De
GetCapLockoutInfo                         UInt32 {LockoutCounter, MaxTries}                                         {De
IsLockedOut                               UInt32 {IsLockedOut}                                                      {De

So we can now see the required ‘SetPhysicalPresenceRequest‘ method. But how do we use it in a MCM Task Sequence in the same manner as the legacy cmdlet?

The answer is below – we need to pipe one cmdlet (Get-CimInstance) into another (Invoke-CimMethod) to achieve the same result as the legacy cmdlet:

powershell.exe -ExecutionPolicy Bypass -Command "Get-CimInstance -Namespace 'root/cimv2/Security/MicrosoftTpm' -ClassName 'Win32_TPM' | Invoke-CimMethod -MethodName 'SetPhysicalPresenceRequest' -Arguments @{Request='14'}"
Run Command Line

Running the newer CIM commands in my MCM ‘Run Command Line‘ task now gives me the same result as the legacy command did and balance is once again restored to the galaxy…

/ JC

ConfigMgr OSD | Windows 10 Disk Partitioning to Correctly Include Recovery Tools Partition

Edit 28/02/24: Updated to reflect increase of Recovery partition to 2048Mb.

How should disks be partitioned for Windows 10 so that the Recovery Partition is configured properly? Turns out the answer isn’t straightforward and that the standard tools provided provided in ConfigMgr by Microsoft don’t quite allow you to do it properly by default…

WinRE is important as it is used by many of the reset features used in Windows 10, especially in Modern Management scenarios. This includes manual recovery, Factory Reset and newer options such as Windows Autopilot Reset.

This partition is used to help recover an OS when not bootable which is why it needs to be located on a separate partition. This partition should be placed immediately after the Windows partition to allow Windows to modify and recreate the partition in the future if future updates (i.e. newer versions of Windows) require a larger Recovery Image. It also allows the device to be rebuilt (i.e., have the OS reinstalled) without affecting the Recovery Partition.

If this resizing/recreation of the Recovery Partition can’t be done during OS upgrades then the ability to use the WinRE environment can be removed and is tricky to remediate afterwards.

To produce this post I have read through a lot of other well-respected blogs and also analysed what a lot of experts have suggested on Twitter etc. This article is the summary of that analysis as well as providing a script written by me which can be used during ConfigMgr Task Sequences to create the required disk partitions correctly. The main resources I used were:

miketerrill.net

garytown.com

Lets start with the recommendations from Microsoft.

UEFI | GPT

For UEFI, Microsoft recommend the following partition layout for Windows 10 (Docs link can be found here):

diagram of default partition layout: system, msr, windows, and recovery
Microsoft Recommended Partition Layout

From this diagram we can see that Microsoft recommend a ‘System‘ partition’ (also known as a EFI System Partition (ESP)) which provides a device with a partition to boot from. It shouldn’t contain any files other than what is intended for booting the device.

‘System’ Partition

Following that is a ‘Microsoft Reserved‘ partition (MSR) which is used for partition management. No user data can be stored on this partition.

‘Microsoft Reserved’ Partition

The next partition is the ‘Windows‘ partition which is obviously used for the Windows Operating System.

‘Windows’ Partition

And the last partition (and the one which causes the most questions) is the ‘Recovery Tools‘ partition which will host a copy of the ‘Windows Recovery Environment’ (WinRE). WinRE is a recovery environment that can repair common causes of unbootable operating systems.

The Recovery Tools partition presents a problem as there is no built-in way in a Task Sequence of ensuring that the Recovery Tools partition is located at the end of the disk (i.e., after the Windows partition) and is configured correctly – this is the main reason for writing this blog post.

The table below summarises the best recommended sizes for each partition based on the Microsoft recommendations and also various experts around the web:

NameSize (Mb)Format
System (EFI)360 fixed sizeFAT32
MSR128 fixed size
Windows (Primary)100% of remaining space on diskNTFS
Recovery 2048 fixed sizeNTFS
UEFI Disk Partitions
UEFI | GPT Format and Partition Disk Step

BIOS | MBR

For traditional BIOS, Microsoft recommend the following partition layout for Windows 10 (Docs link can be found here). Note that ‘BIOS | MBR’ is now considered legacy and has limited use case scenarios with Modern Device Management. Many modern security controls are based on the newer UEFI firmware and so ‘BIOS | MBR’ and is therefore rarely used:

diagram of default partition layout: system, windows, and recovery

From this diagram we can see that Microsoft again recommends a ‘System‘ partition which is used to boot the device.

‘System’ Partition

Following that is the ‘Windows‘ partition to be used for the Windows 10 operating system.

‘Windows’ Partition

And the final partition is once again the ‘Recovery Tools‘ partition which was mentioned previously in the UEFI section above.

The table below summarises the best recommended sizes for each partition based on the Microsoft recommendations and also various experts around the web:

NameSize (Mb)Format
System360 fixed sizeNTFS
Windows (Primary)100% of remaining space on diskNTFS
Recovery 2048 fixed sizeNTFS
Traditional BIOS Disk Partitions
BIOS | MBR Format and Partition Disk Step

Recovery Tools Partition Solution

To create the Recovery Tools partition in the location and size required we need to use ‘Diskpart’ to create the Recovery Partition once the built-in ‘Format and Partition Disk‘ step has been completed.

The way this is done is to shrink the ‘Windows’ partition by the size needed (in this case 984Mb) and create a new partition with that newly-available space called ‘Recovery’.

For UEFI deployments, the ‘Recovery Tools’ partition needs to have it’s ‘ID‘ set to ‘de94bba4-06d1-4d40-a16a-bfd50179d6ac‘, be given the GPT attribute of ‘0x8000000000000001‘ and needs to be set to have a Partition Type of ‘27‘ so that it is hidden.

To accomplish this I wrote a PowerShell script to run Diskpart with the required settings. The script should be added to the Task Sequence just after the built-in ‘Format and Partition Disk’ step as per the images below:

Location of ‘Create Recovery Partition’ Step

The script works best when entered as a PowerShell script as part of a ‘Run PowerShell Script‘ task in a Task Sequence:

‘Create Recovery Partition’ Script Task

The script for automating the creation of the Recovery Partition is embedded below. It will detect if the device is configured for BIOS or UEFI and create the partition accordingly:

<#
.DESCRIPTION
    Script to create Recovery Partition during OSD and hide System Partition (if needed) on MBR

    This script requires the following optional Boot Image components: 'WinPE-DismCmdlets', 'WinPE-PowerShell', 'WinPE-StorageWMI'
.EXAMPLE
    PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1
.NOTES

    VERSION     AUTHOR              CHANGE
    1.5         Jonathan Conway     Initial script creation
#>

# Loads the Task Sequence environment
$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment

# Configures script log path to match tsenv logs folder
$LogPath = $tsenv.Value("_SMSTSLogPath")

# Determines if firmware configured as UEFI
$UEFI = $tsenv.Value("_SMSTSBootUEFI")

# Get OS Disk information
[string]$OSDrivePartitionNumber = (Get-Disk | Where-Object {$PSItem.BusType -ne 'USB'} | Get-Partition | Where-Object {$PSItem.Size -gt '5GB' -and $PSItem.Type -eq 'Basic'}).PartitionNumber

# Recovery partition size in Mb
$RecoveryPartitionSize = '2048'

If ($UEFI -eq $true) {

    'select disk 0',
    'list partition',
    "select partition $OSDrivePartitionNumber",
    "shrink desired=$RecoveryPartitionSize minimum=$RecoveryPartitionSize",
    'create partition primary',
    'format quick fs=ntfs label=Recovery',
    'set id="de94bba4-06d1-4d40-a16a-bfd50179d6ac"',
    'gpt attributes=0x8000000000000001',
    'list partition' | diskpart | Tee-Object -FilePath "$LogPath\Pwsh-OsdDiskpart.log"
}

else {
    'select disk 0',
    'list partition',
    "select partition $OSDrivePartitionNumber",
    "shrink desired=$RecoveryPartitionSize minimum=$RecoveryPartitionSize",
    'create partition primary',
    'format quick fs=ntfs label=Recovery',
    'set id=27',
    'list partition',
    'select partition 1', 'set id=17', 'list partition' | diskpart | Tee-Object -FilePath "$LogPath\Pwsh-OsdDiskpart.log"
}

The script uses variables for the OS Drive Partition and Recovery Partition Size so both of these can be modified according to specific requirements in the event that this is needed.

So this solution should allow you to correctly configure the disk partitions needed to install Windows 10 and also ensure that a functional Recovery Tools partition is always present.

/ JC

PowerShell Gather: Moving Away from MDT-Integrated Task Sequences in Microsoft Configuration Manager (MCM)

I’ve been a huge fan of MDT over the years and still use it to create my Windows Reference images to this day as it’s so straightforward for me to make tweaks to a WIM file if I need to.

Historically I have always recommended and implemented MDT-Integrated Task Sequences in Configuration Manager to take advantage of all the additional capabilities that MDT provides.

Recently though I have started to move to using a standard MCM OSD Task Sequence as they are so much more simple and require less maintenance.

The most useful thing from MDT Integration that I use day-to-day for OSD is the ‘MDT Gather’ step to collect information about the device and deployment at various points in the Task Sequence. This allows various aspects of a deployment to be controlled dynamically based on numerous pre-defined variables such as the classic IsDesktop/IsLaptop scenarios etc.

The downside to this is the steps require a MCM Package to be created and maintained plus it adds unnecessary time to the deployment when downloading the Toolkit Package.

It is possible to retain this useful capability by replacing the MDT Toolkit/Gather steps with a PowerShell script which can be added directly into the ‘Run PowerShell Script’ Task Sequence step and that’s why I’m here writing this post 🙂

I found a script which was created by Johan Schrewelius (with contributions from various others) which did the majority of what I wanted. His script can be accessed on the Technet PowerShell Gallery (Link).

By reworking this script and adding functionality that I specifically needed, I now have a lightweight and solid ‘Gather’ solution which can be easily added to any MCM Task Sequence.

v1.0 of the script collects the following information. The example is one of my lab devices so you can see what the info looks like. I expect this list to expand over time as new requirements crop up:

Architecture = X64
AssetTag = CZCXXXXXXX
BIOSReleaseDate = 12/25/2019 00:00:00 -BIOSVersion = N01 Ver. 02.45
BitlockerEncryptionMethod = AES_256
DefaultGateway = 192.168.1.1
IPAddress = 192.168.1.201
IsBDE = True
IsCoffeeLakeOrLater = False
IsDesktop = True
IsLaptop = False
IsOnBattery = False
IsOnEthernet = True
IsServer = False
IsTablet = False
IsVM = False
MacAddress = 48:0F:CF:46:09:F5
Make = HP
Memory = 49031.58203125
Model = HP EliteDesk 800 G2 SFF
OSBuildNumber = 17763.1158
OSCurrentBuild = 17763
OSCurrentVersion = 10.0.17763
ProcessorFamily = 6700
ProcessorManufacturer = GenuineIntel
ProcessorName = Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
ProcessorSpeed = 3408
Product = 8054
SerialNumber = CZCXXXXXXX
UUID = D41CCC6A-E086-13G5-9C43-BC0000EE0000
Vendor = HP
VMPlatform = N/A

I’ve been using it with customers for a while and am happy now that it’s robust/mature enough to be shared on GitHub for others to use as well if they want to:

https://github.com/jonconwayuk/PowerShell_Gather

It can be added into a Task Sequence as per the image below using the ‘Run PowerShell Script’ step with the Execution Policy set to ‘Bypass’. Each time it runs, it will add the collected variables into the running Task Sequence environment and can be used throughout the Task Sequence:

‘Run PowerShell Script’ Step with Pwsh-Gather.ps1 script added

Below is an example of how variables can be utilised – in this example the condition is to control some BitLocker tasks which I only wanted to run on physical devices which are also laptops:

Conditions using Gather Variables

It also creates a log file (Pwsh-Gather.log) in the standard Task Sequence logging directory defined as the built in variable “_SMSTSLogPath” which can be reviewed using cmtrace.exe.

For testing, the script can be run locally on a device by using the ‘-Debug’ parameter as per the example below from an ‘Administrator’ PowerShell prompt:

PS C:\Users\Administrator\Documents\PowerShell_Gather> .\Pwsh-Gather.ps1 -Debug

Feel free to start using the script and let me know if there are any improvements or additions that you’d like to see and I’ll try and accommodate them when time permits. Hopefully people find it useful!

/ JC

MCM Software Updates | Load Balancing with Even and Odd Collections

Recently a customer asked me to help BAU Support find a way to reduce the impact of deploying Windows 10 Software Updates (LCU) over their Wireless LAN in a particular building with a large number of clients. The last round of patching had pretty much destroyed their WiFi during the deployment. I decided the first thing to do was to load balance the deployments by devising a way of splitting the clients roughly into two groups.

I created MCM Collections based on Odd and Even numbers i.e. if a device ends in an odd number become a member of one collection and if it ends in an even number it becomes a member of another collection.

To accomplish this I used square brackets to define a range of characters as part of the WQL query for the Collections: One for devices ending in an odd number and one for devices ending in an event number.

This query results in devices ending in odd numbers:

/* Odd Numbers */
select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,
SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,
SMS_R_SYSTEM.ResourceDomainORWorkgroup,
SMS_R_SYSTEM.Client from SMS_R_System
where SMS_R_System.Name like "%[13579]"

And another collection returning only devices ending in even numbers:

/* Even Numbers */
select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,
SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,
SMS_R_SYSTEM.ResourceDomainORWorkgroup,
SMS_R_SYSTEM.Client from SMS_R_System
where SMS_R_System.Name like "%[02468]"

The next issue was that not all client device names ended with a number – some ended with a letter. Fantastic…

To cater for this I divided the alphabet by odd and even letters and added the odd letters to odd query and the even letters to the even query.

The resultant query returned only devices ending in odd numbers or letters:

/* Odd Numbers and Odd Letters */
select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,
SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,
SMS_R_SYSTEM.ResourceDomainORWorkgroup,
SMS_R_SYSTEM.Client from SMS_R_System
where SMS_R_System.Name like "%[13579acegikmoqsuwy]"

And conversely, the following query returned only devices which ended with an even number or letter:

/* Even Numbers and Even Letters */
select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,
SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,
SMS_R_SYSTEM.ResourceDomainORWorkgroup,
SMS_R_SYSTEM.Client from SMS_R_System
where SMS_R_System.Name like "%[02468bdfhjlnprtvxz]"

This isn’t the perfect solution (there won’t be an exact 50% split of devices) but it was good enough for my customer as a tactical solution.

Used in conjunction with MCM Phased Deployments etc. should see a large reduction in the load on the WLAN and allow users to work normally during patching in the future.

/ JC