Tag Archives: PowerShell

Understanding the ‘Correct’ File Version used for Intune Win32 App Detection Method

Depending on the developer for an application, various & differing version numbers can be added into the metadata of an application/executable/binary which can sometimes be confusing when creating manual detecton rules as part of an Intune Win32 App.

A good example of this is ‘CMTrace.exe’ from Microsoft. The screenshot below shows the ‘Details’ tab of ‘CMTrace.exe’ file properties with ‘File version’ highlighted:

You can see from the screenshot that the ‘File Version’ displayed is ‘5.0.9068.1000′ which is what I initially used for my Detection Rule as part of a CMTrace Win32 App in Intune:

Unfortunately this resulted in a continuous loop of ‘CMTrace.exe’ being reinstalled as it wasn’t being detected correctly by Intune – this was visible in the log file below:

C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log. 

Turns out that Intune actually uses a different property of the file metadata to determine the version number of a file. This is documented in the Microsoft article below:

https://learn.microsoft.com/en-us/mem/intune/apps/apps-win32-troubleshoot#detecting-the-win32-app-file-version-by-using-powershell

Two quick & easy ways to retrieve the correct ‘Version’ value to use in a Detection Rule are shown in the PowerShell commands below. Both commands are querying the same file in the same location on a reference computer i.e., ‘CMTrace.exe’ in the ‘C:\Windows’ directory:

[System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Windows\cmtrace.exe").FileVersion

(Get-Item -Path "C:\Windows\CMTrace.exe").VersionInfo.FileVersion

The value returned from these queries was ‘5.00.9068.1000′.

The additional zero in the version number was the difference that broke the configured Detection Rule.

Using the correct value returned by either of these two PowerShell commands (they are both returning the same information from .NET) will match the value retrieved by Intune when checking if the Win32 App is installed or not.

This will result in the Win32 App being installing once and then correctly being detected as installed thereafter i.e., the Detection Rule will now successfully work as expected.

/ JC

Registry Changes with PowerShell

It’s a common requirement to configure Registry values on an endpoint when building or configuring them.

I like to use PowerShell to do this as it’s easy to document and keep track of changes.

One issue with the standard PowerShell method (Set-ItemProperty) to do this is that you usually need a key to exist before you can add values into it. This usually means having to figure out if a key exists and if not, running several ‘New-Item’ commands to create the key (and potentially the tree/path to the key) itself first.

Fine for a few settings but not ideal when you have 10’s or even 100’s of registry settings to deploy.

Using a .REG file avoids this as it imports the key structure and the key values too. But this adds obfuscation into mix as you need to open each .REG file to see what it contains and that can be unintuitive sometimes.

The solution to this is to call registry changes directly using the “[Microsoft.Win32.Registry]::SetValue” .NET method as this behaves in the same way as importing .REG files in that it will create the key and path to the key regardless of whether the key/path already exists. Quick and easy (also highly performant!).

The format of these commands wasn’t initially obvious from Microsoft documentation but after a lot of trial and error, frustration and profuse swearing, I can provide the following examples for the most common types of Registry changes:

<#
.DESCRIPTION
    Script to configure Registry Items
.EXAMPLE
    Powershell.exe -ExecutionPolicy Bypass -File <ScriptName>.ps1
.NOTES
    VERSION     AUTHOR              CHANGE
    1.0         Jonathan Conway     Initial Script creation
    [Microsoft.Win32.Registry]::SetValue("keyName", "valueName", "value", [Microsoft.Win32.RegistryValueKind]::DWord)
    [Microsoft.Win32.Registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Example", "ExampleValueName", "ExampleValue", [Microsoft.Win32.RegistryValueKind]::String)
    [Microsoft.Win32.Registry]::LocalMachine.DeleteSubKey("SOFTWARE\Example")
   
    Valid Root names are: "HKEY_CURRENT_USER", "HKEY_LOCAL_MACHINE", "HKEY_CLASSES_ROOT", "HKEY_USERS", "HKEY_PERFORMANCE_DATA", "HKEY_CURRENT_CONFIG", and "HKEY_DYN_DATA".
    Valid "RegistryValueKind" values: "String", "ExpandString", "Binary", "DWord", "MultiString" and "QWord".
#>
# Binary Value
$ExampleBinaryValue = [byte[]](150, 18, 6, 230, 16, 0, 0, 0)
[Microsoft.Win32.Registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Test", "BinaryExampleName", [byte[]]$ExampleBinaryValue, [Microsoft.Win32.RegistryValueKind]::Binary)
# Dword Value
[Microsoft.Win32.Registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Test", "DwordExampleName", [int]"1", [Microsoft.Win32.RegistryValueKind]::DWord)
# String Value
[Microsoft.Win32.Registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Test", "StringExampleName", [string]"ExampleValue", [Microsoft.Win32.RegistryValueKind]::String)
# MultiString Value
$MultiStringExampleValue = @(
    "ExampleValue1",
    "ExampleValue2",
    "ExampleValue3",
    "ExampleValue4"
)
[Microsoft.Win32.Registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Test", "ExampleName", [string[]]$MultiStringExampleValue, [Microsoft.Win32.RegistryValueKind]::MultiString)

Posting this so I never have to go through the ordeal of getting these commands formatted correctly… And of course to benefit the community too! πŸ˜‰

/ JC

Intune | Force Microsoft Edge Update to Latest Version During Windows Autopilot

Quick and simple post today. Had a customer deploying Windows 10 IoT Enterprise LTSC 2021 (yes I am aware that IoT Enterprise and LTSC are not officially supported for Windows Autopilot at time of writing but it works fine so… ) and there was a requirement to update the version of Edge included in this version of Windows so that it was at a version which supported some of the more recent Intune Configuration Profile policy settings. Should this not happen then the policies would not apply until such time as Edge updated itself which may be some time after a user had logged into the device.

To accomplish this I wrapped the following PowerShell script into a Win32 app and had it configured as a Blocking application on the Enrolment Status Page (ESP) being used for Autopilot.

The result is Microsoft Edge updating to the latest available version before any users log in for the first time.

<#
.DESCRIPTION
    PowerShell script to force update of Microsoft Edge Stable
.EXAMPLE
    PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1
.NOTES
    VERSION     AUTHOR              CHANGE
    1.0         Jonathan Conway     Initial script creation
#>

$Exe = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exe"
$Arguments = "/silent /install appguid={56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}&appname=Microsoft%20Edge&needsadmin=True"
return (Start-Process $($Exe.FullName) -ArgumentList $Arguments -NoNewWindow -PassThru -Wait).ExitCode

Be sure to configure the detection method according to your environment. For me, I set this to a version of “Greater than or equal to: 100.0.0000.00” to detect the installation but you may want to use a higher version number depending on your own circumstances:

Let me know if this works for you (or if you have any issues with the script) in the comments.

/ JC

Intune Proactive Remediation: Detect & Remove User-installed Instances of Zoom

Had a requirement to detect and remove any user installations of Zoom (i.e. installed using standard user permissions and located in the user profile) via Intune. The supported route for uninstalling Zoom is use a Zoom-provided tool called ‘CleanZoom.exe’ so the script checks for that tool being present and if not, downloads and extracts it directly from Zoom before running the tool to remove any user installations of Zoom. Also needed a log file to show when this has been done from the client (this can obviously be removed if not needed).

Proactive Remediations to the rescue again!

Detection:

<#
.DESCRIPTION
	Proactive Remediation | Detection
.EXAMPLE
	PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1
.NOTES
	VERSION     AUTHOR              CHANGE
    1.0         Jonathan Conway     Initial script creation
#>

# Discovery
try {
    # Run Test and store as variable
    $Test = Get-ChildItem -Path "C:\Users\" -Filter "Zoom.exe" -Recurse -Force -ErrorAction SilentlyContinue

    # Check where test is compliant or not - if no instances of Zoom are discovered then mark as 'Compliant' and exit with 0
    if ($null -eq $Test) {
        Write-Output "Compliant"
        exit 0
    }
    # If instances of Zoom are discovered then mark as 'Non Compliant' and exit with 1
    else {
        Write-Warning "Non Compliant"
        exit 1
    }
}

catch {
    # If any errors occur then return 'Non Compliant'
    Write-Warning "Non Compliant"
    exit 1
}

Remediation:

<#
.DESCRIPTION
	Proactive Remediation | Remediation
.EXAMPLE
	PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1
.NOTES
	VERSION     AUTHOR              CHANGE
    1.0         Jonathan Conway     Initial script creation
#>

# Logging
$LogPath = "C:\Support\Zoom\"
Start-Transcript -Path $LogPath\ZoomCleanup.log -Append -NoClobber

# Variables
$CleanZoomTool = "C:\Support\Zoom\CleanZoom.exe"

# Check to see if 'C:\Support\Zoom' exists
$CheckZoomFolder = Test-Path -Path "C:\Support\Zoom\" -PathType Container

# If 'C:\Support\Zoom' folder does not exist then create it
if ($CheckZoomFolder -eq $false) {

	# Create folder
	Write-Output "'C:\Support\Zoom' folder does not exist - creating it"
	New-Item -Path "C:\Support" -Name "Zoom" -ItemType "Directory" -Force

}
else {
	Write-Output "'C:\Support\Zoom' folder exists - continuing"
}

# Check if CleanZoom.exe exists on the device
$CheckZoomClean = Test-Path -Path $CleanZoomTool -PathType "Leaf"

# If CleanZoom.exe does not exist on the device - download from Zoom website and extract locally
if ($CheckZoomClean -eq $false) {

	Write-Output "'C:\Support\Zoom\CleanZoom.exe' does not exist - downloading and extracting it"
	Invoke-WebRequest -Uri "https://assets.zoom.us/docs/msi-templates/CleanZoom.zip" -OutFile "C:\Support\Zoom\CleanZoom.zip"
	Expand-Archive -Path "C:\Support\Zoom\CleanZoom.zip" -DestinationPath "C:\Support\Zoom" -Force
	Remove-Item -Path "C:\Support\Zoom\CleanZoom.zip" -Force

}
else {
	Write-Output "'C:\Support\Zoom\CleanZoom.exe' exists - continuing"
}

try {
	# Run CleanZoom.exe to remove any installed instances of Zoom client in User Profiles
	Write-Output "Running CleanZoom.exe to remove Zoom instances from User Profile areas"
	Start-Process -FilePath $CleanZoomTool -ArgumentList "/silent"
	exit 0
}
catch {
	Write-Output "CleanZoom.exe failed to run"
	exit 1
}

Stop-Transcript

/ JC

Intune Proactive Remediation: BitLocker Key Escrow to Azure AD After MCM OSD Task Sequence

Recently had a customer requirement to encrypt Windows 10 devices using a MCM Task Sequence and then have the Recovery Keys escrowed into AAD once an Intune Drive Encryption policy was applied via Co-management workload shift (Endpoint Protection).

By default, Windows will escrow to where you tell it in the Task Sequence and not escrow into AAD. In my case the Task Sequence was storing the Recovery Key into on-prem Active Directory.

The Discovery script checks Event Viewer for an Event 845 including the text “was backed up successfully to your Azure AD” having been logged in the last 7 days (this can obviously be amended to suit individual requirements).

If non-compliant then the Remediation script forces the key to be escrowed using the ‘BackupToAAD-BitLockerKeyProtector’ PowerShell cmdlet.

Detection:

<#
.DESCRIPTION
    Script to check for BitLocker Key escrow into Azure AD
.EXAMPLE
    PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1
.NOTES
    VERSION     AUTHOR              CHANGE
    1.0         Jonathan Conway     Initial script creation
#>

# Check for Event 845 in BitLocker API Management Event Log over last 7 days - if contains text "was backed up successfully to your Azure AD" then Detection is complete
try {
    $Result = Get-WinEvent -FilterHashTable @{LogName = "Microsoft-Windows-BitLocker/BitLocker Management"; StartTime = (Get-Date).AddDays(-7) } | Where-Object { ($_.Id -eq "845" -and $_.Message -match "was backed up successfully to your Azure AD") } | Format-Table -Property "Message"
    $ID = $Result | Measure-Object

    if ($ID.Count -ge 1) {
        Write-Output "BitLocker Recovery Key escrow to Azure AD succeeded = Compliant"
        exit 0
    }

    # If Event is not detected then mark as 'Non Compliant' and exit with 1
    else {
        Write-Warning "BitLocker Escrow Event Missing = Non Compliant"
        exit 1
    }
}

catch {
    Write-Warning "An error occurred = Non Compliant"
    exit 1
}

Remediation:

<#
.DESCRIPTION
    Script to remediate BitLocker Key escrow into Azure AD
.EXAMPLE
    PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1
.NOTES
	VERSION     AUTHOR              CHANGE
    1.0         Jonathan Conway     Initial script creation
#>

# Escrow BitLocker Recovery Key for OSDrive into Azure AD
$BitLockerVolume = Get-BitLockerVolume -MountPoint $env:SystemRoot
$RecoveryPasswordKeyProtector = $BitLockerVolume.KeyProtector | Where-Object { $_.KeyProtectorType -like "RecoveryPassword" }
BackupToAAD-BitLockerKeyProtector -MountPoint $BitLockerVolume.MountPoint -KeyProtectorId $RecoveryPasswordKeyProtector.KeyProtectorId -ErrorAction SilentlyContinue

/ JC

Finding Your Windows 10 OEM Product Key Embedded In Firmware/WMI

To retrieve the OEM Windows 10 Product Key which is usually stored within a devices WMI repository you can use the following PowerShell command:

(Get-CimInstance -ClassName SoftwareLicensingService).OA3xOriginalProductKey

To retrieve and then install an OEM Windows 10 Product Key, the following PowerShell can be used:

<#
.DESCRIPTION
    Script to activate Windows 10 using OEM licence key
.EXAMPLE
    PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1
.NOTES
    Author(s):  Jonathan Conway
    Modified:   08/09/2021
    Version:    1.0
#>

$OemProductKey = (Get-CimInstance -ClassName "SoftwareLicensingService").OA3xOriginalProductKey
$cscript = 'C:\Windows\System32\cscript.exe'

# Install Product Key
return (Start-Process $cscript -ArgumentList "/B C:\Windows\System32\slmgr.vbs -ipk $OemProductKey" -NoNewWindow -PassThru -Wait).ExitCode
return (Start-Process $cscript -ArgumentList "/B C:\Windows\System32\slmgr.vbs -ato" -NoNewWindow -PassThru -Wait).ExitCode

You can also use WMI to accomplish the same things which is demonstrated in the PowerShell script below:

<#
.DESCRIPTION
    Script to activate Windows 10 using OEM licence key
.EXAMPLE
    PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1
.NOTES
    Author(s):  Jonathan Conway
    Modified:   10/05/2022
    Version:    1.0
#>

# Variables
$SLS = Get-CimInstance -ClassName "SoftwareLicensingService"
$OemProductKey = (Get-CimInstance -ClassName "SoftwareLicensingService").OA3xOriginalProductKey

# Display and Activate Product Key
Write-Output "$OemProductKey"
$SLS | Invoke-CimMethod -MethodName "InstallProductKey" -Arguments @{ ProductKey = "$OemProductKey" }
$SLS | Invoke-CimMethod -MethodName "RefreshLicenseStatus"

# JC

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

PowerShell: Automate Naming of Captured WIM File During MDT Reference Image Creation

If you’re Old Skool like me and still use MDT to produce Windows 10 Reference Images then this script may be useful to save some time and hassle.

The script basically automates the creation of the filename for the backup WIM file so that all that is required to produce a new image bi-annually (or as often as you like) is to run a Build & Capture Task Sequence which (providing the VM has access to WSUS or Microsoft Update) will include the all the latest patches.

It produces a filename in the following format which includes the Windows 10 version being captured, architecture, language and the date.

W10X64_20H2_en-GB_19042.572_2020-10-22_1525.wim

Because the date includes the time the image is captured, the filename will always be unique so there will never be an occasion where the image can’t be captured to a pre-existing WIM file being present with the same name.

The net result is that the Reference Image creation can be as simple as booting a VM, choosing a Task Sequence then collecting the WIM file it produces at the end of the process.

The script produces an MDT variable called ‘%WimFileName%‘ which is then used to populate the ‘BackupFile‘ property in the Task Sequence – this is demonstrated in the images below:

Configure: Set WIM Filename
Set: BackupFile

The script content is embedded below. Copy and paste into a blank text file and save as ‘Pwsh-SetWimFilename.ps1‘ and copy it into your MDT Deployment share in the following location:

DeploymentShare\Scripts\Custom

Script Content:

<#
.DESCRIPTION
    Script to automate the naming of the WIM File Name during MDT OSD
.EXAMPLE
    PowerShell.exe -ExecutionPolicy ByPass -File <ScriptName>.ps1 [-Debug]
.NOTES
    Author(s):  Jonathan Conway
    Modified:   17/11/2021
    Version:    1.5

    Option [-Debug] switch can be run locally to output results to screen to test WIM File Name is correct
#>

Param (
    [Switch]$Debug
)

Begin {

    # Variables: Information from Registry
    $OsRegistryInfo = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    [string]$DisplayVersion = $OsRegistryInfo.DisplayVersion
    [string]$ReleaseId = $OsRegistryInfo.ReleaseId
    [string]$Ubr = $OsRegistryInfo.UBR
    [string]$EditionId = $OsRegistryInfo.EditionID
    [string]$OsCurrentBuildNumber = $OsRegistryInfo.CurrentBuildNumber

    # Variables: Change 'ReleaseID' to new Windows Release naming format if Windows 10 20H2 or later
    if ($ReleaseId -gt '2004' -and $ReleaseId -lt '2009') {

        if ($ReleaseId -match "^(..)(01|02|03|04|05|06)$") {
            [string]$ReleaseId1stHalf = $ReleaseId.Substring(0, 2)
            [string]$ReleaseId2ndHalf = $ReleaseId.Substring(2, 2)
            [string]$ReleaseId2ndHalfReplaced = $ReleaseId2ndHalf -replace "$ReleaseId2ndHalf", "H1"
            [string]$ReleaseId = "$ReleaseId1stHalf" + "$ReleaseId2ndHalfReplaced"
        }

        if ($ReleaseId -match "^(..)(07|08|09|10|11|12)$") {
            [string]$ReleaseId1stHalf = $ReleaseId.Substring(0, 2)
            [string]$ReleaseId2ndHalf = $ReleaseId.Substring(2, 2)
            [string]$ReleaseId2ndHalfReplaced = $ReleaseId2ndHalf -replace "$ReleaseId2ndHalf", "H2"
            [string]$ReleaseId = "$ReleaseId1stHalf" + "$ReleaseId2ndHalfReplaced"
        }

    }

    elseif ($ReleaseId -ge '2009') {
        $ReleaseId = $DisplayVersion
    }

    # Variables: Information from WMI
    $OsWmiInfo = Get-CimInstance -ClassName 'Win32_OperatingSystem'

    # Variables: OS 'Caption' information
    $Caption = $OsWmiInfo.Caption
    [String]$RegExPattern = '(Microsoft\ (Windows|Hyper-V)\ (10|11|Server\ (2016.*?|2019.*?)))'
    [String]$MachineOS = ($Caption | Select-String -AllMatches -Pattern $RegExPattern | Select-Object -ExpandProperty 'Matches').Value

    # Variables: Media Language
    [string]$OsLanguageNumberCode = $OsWmiInfo.OSLanguage

    if ($OsLanguageNumberCode -eq '2057') {
        $OsLanguage = 'en-GB'
    }
    if ($OsLanguageNumberCode -eq '1033') {
        $OsLanguage = 'en-US'
    }

    # Variables: Date Information
    $BuildDate = Get-Date -Format "yyyy-MM-dd_HHmm"

    # Variables: OS Architecture
    if ($OsWmiInfo.OSArchitecture -eq '64-bit') {
        $Architecture = 'X64'
    }
    if ($OsWmiInfo.OSArchitecture -eq '32-bit') {
        $Architecture = 'X86'
    }

}

Process {

    # Microsoft Hyper-V Server
    if ($MachineOS -like 'Microsoft Hyper-V Server*') {
        # Variables: Set OS Prefix
        $HypervPrefix = 'HVS'

        # Variables: Set Windows Server Version
        $WindowsServerVersion = $MachineOS.TrimStart("Microsoft Hyper-V Server")

        # Hyper-V Server: Create Wim File Name string
        $WimFileName = "$HypervPrefix" + "$WindowsServerVersion" + "$Architecture" + '_' + "$OsLanguage" + '_' + "$OsCurrentBuildNumber" + '.' + "$Ubr" + '_' + "$BuildDate" + '.' + 'wim'
    }

    # Microsoft Windows 10
    if ($MachineOS -like 'Microsoft Windows 10*') {
        # Variables: Set OS Prefix
        $Os = 'W10'

        # Variables: OS Edition
        if ($EditionId -eq 'Enterprise') {
            $Edition = 'ENT'
        }
        if ($EditionId -eq 'IoTEnterprise') {
            $Edition = 'IOT'
            $ReleaseId = $OsRegistryInfo.DisplayVersion
        }
        if ($EditionId -eq 'EnterpriseS') {
            $Edition = 'IOT'
            $Channel = 'LTSC'
        }
        if ($EditionId -eq 'Professional') {
            $Edition = 'PRO'
        }

        if ($EditionId -eq 'EnterpriseS') {
            # Windows 10 IoT LTSC: Create Wim File Name string
            $WimFileName = "$Os" + "$Architecture" + '_' + "$Edition" + '_' + "2019" + '_' + "$Channel" + '_' + "$OsLanguage" + '_' + "$OsCurrentBuildNumber" + '.' + "$Ubr" + '_' + "$BuildDate" + '.' + 'wim'
        }
        else {
            # Windows 10: Create Wim File Name string
            $WimFileName = "$Os" + "$Architecture" + '_' + "$Edition" + '_' + "$ReleaseId" + '_' + "$OsLanguage" + '_' + "$OsCurrentBuildNumber" + '.' + "$Ubr" + '_' + "$BuildDate" + '.' + 'wim'
        }

    }

    # Microsoft Windows 11
    if ($MachineOS -like 'Microsoft Windows 11*') {
        # Variables: Set OS Prefix
        $Os = 'W11'

        # Variables: Set Display Version
        $OsDisplayVersion = $OsRegistryInfo.DisplayVersion

        # Variables: OS Edition
        if ($EditionId -eq 'Enterprise') {
            $Edition = 'ENT'
        }
        if ($EditionId -eq 'IoTEnterprise') {
            $Edition = 'IOT'
        }
        if ($EditionId -eq 'Professional') {
            $Edition = 'PRO'
        }

        # Windows 10: Create Wim File Name string
        $WimFileName = "$Os" + "$Architecture" + '_' + "$Edition" + '_' + "$OsDisplayVersion" + '_' + "$OsLanguage" + '_' + "$OsCurrentBuildNumber" + '.' + "$Ubr" + '_' + "$BuildDate" + '.' + 'wim'
    }

    # Microsoft Windows Server
    if ($MachineOS -like 'Microsoft Windows Server*') {
        # Variables: Set OS Prefix
        $ServerPrefix = 'WS'

        # Variables: Set Windows Server Version
        $WindowsServerVersion = $MachineOS.TrimStart("Microsoft Windows Server")

        # Windows Server: Create Wim File Name string
        $WimFileName = "$ServerPrefix" + "$WindowsServerVersion" + "$Architecture" + '_' + "$OsLanguage" + '_' + "$OsCurrentBuildNumber" + '.' + "$Ubr" + '_' + "$BuildDate" + '.' + 'wim'
    }

}

End {

    # If Debug is true then write WIM file name to host
    if ($Debug) {
        Write-Host "Caption is:         `"$Caption`"" -BackgroundColor 'Green' -ForegroundColor 'Black'
        Write-Host "MachineOS is:       `"$MachineOS`"" -BackgroundColor 'Green' -ForegroundColor 'Black'
        Write-Host "MachineOS is:       `"$EditionId`"" -BackgroundColor 'Green' -ForegroundColor 'Black'
        Write-Host "WIM File Name is:   `"$WimFileName`"" -BackgroundColor 'Green' -ForegroundColor 'Black'
    }

    else {
        # Set MDT Task Sequence Variable to be used to populate 'BackupFile'
        $tsenv:WimFileName = "$WimFileName"
    }

}

It is possible to test the output of the script by copying the script onto a device and running it with the ‘-debug’ switch. This will display the WIM Filename in the PowerShell console so you can check to see if it is correct:

Debug Script Output

/ 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

Check TPM Status from the Command Line (Enabled | Activated | Owned)

Quick and simple way to see if the TPM on a computer is Enabled, Activated and Owned – all of which are required before using them for BitLocker:

wmic /namespace:\\root\cimv2\security\microsofttpm path win32_tpm get IsEnabled_InitialValue
wmic /namespace:\\root\cimv2\security\microsofttpm path win32_tpm get IsActivated_InitialValue
wmic /namespace:\\root\cimv2\security\microsofttpm path win32_tpm get IsOwned_InitialValue

As long as they all return as “True” you’re good to go.

/ JC