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
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
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‘.
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:
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:
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!!!!
“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:
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:
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…
A customer recently had a requirement for rebuilds to be done in remote sites via USB flash drives configured as MCM Bootable Media due to a lack of local MCM Distribution Points and PXE Boot capability.
Using devices in UEFI mode with BitLocker enabled makes this tricky when the Boot Image associated with the Task Sequence becomes out of sync with the Boot Image on the USB media. If the boot images don’t match then MCM attempts to pre-stage onto the local disk and fails as the OSDisk is unavailable due to it being encrypted with BitLocker (the drive appears as “RAW” and cannot be accessed) and none of the other partitions are large enough or available.
I worked around this by creating a PowerShell PreStart script and adding it to the Boot Media ISO image. The script runs before the Task Sequence begins. It creates a Diskpart configuration text file on the fly in the ‘X:\Windows\Temp’ folder of the running WinPE. After creating the Diskpart configuration file, it then runs Diskpart referencing the configuration file in order to create suitably-sized/lettered partitions to successfully boot from using UEFI and that are also accessible for the Task Sequence to download and pre-stage the latest Boot Image if it’s required (i.e. if it’s different to the boot image on the USB).
Problem solved!
The command for the PreStart script that I used was:
And the PowerShell code contained with PreStart.ps1 is shown below:
<#
.DESCRIPTION
Configures GPT disk layout using DiskPart.exe to avoid Boot Image mismatching when using MCM Bootable Media
.EXAMPLE
PowerShell.exe -ExecutionPolicy ByPass -File .ps1
.NOTES
Author: Jonathan Conway
Modified: 06/04/2019
Version: 1.0
#>
# Display warning and request confirmation from engineer
$Shell = New-Object -ComObject "WScript.Shell"
$Button = $Shell.Popup("Proceeding will wipe all local data from all local drives. Hold Power Button until device powers off to cancel. Click OK to proceed.", 0, "WARNING", 0)
# Set variables
$DiskPartFile = "X:\Windows\Temp\DiskpartConfig.txt"
if (Get-Volume | Where-Object {$_.DriveLetter -eq 'C' -and $_.DriveType -eq 'Removable'}) {
Get-Partition -DriveLetter 'C' | Set-Partition -NewDriveLetter 'U'
}
# Create contents of DiskPart configuration file
Write-Output "SELECT DISK 0" | Out-File -Encoding utf8 -FilePath "$DiskpartFile"
Write-Output "CLEAN" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "CONVERT GPT" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "CREATE PARTITION EFI SIZE=200" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "ASSIGN LETTER=S" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "FORMAT QUICK FS=FAT32" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "CREATE PARTITION MSR SIZE=128" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "CREATE PARTITION PRIMARY" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "ASSIGN LETTER=C" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "FORMAT QUICK FS=NTFS" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
Write-Output "EXIT" | Out-File -Encoding utf8 -FilePath "$DiskpartFile" -Append
# Run DiskPart
Start-Process -FilePath "diskpart.exe" -ArgumentList "/s $DiskPartFile" -Wait
In my environment this formats the disks in a way which allows my Task Sequence to progress whatever state the UEFI partitions are in (i.e. BitLocker enabled or not).
A pop up warning is shown on screen stating:
“Proceeding will wipe all local data from all local drives. Hold Power Button until device powers off to cancel. Click OK to proceed“.
Clicking OK continues ahead and starts the Diskpart process before progressing to the Task Sequence selection screen π