Skip to content

Instantly share code, notes, and snippets.

@SkipToTheEndpoint
Created August 5, 2022 18:08
Show Gist options
  • Save SkipToTheEndpoint/7bf177daea583cb61720c96c7c2f55a8 to your computer and use it in GitHub Desktop.
Save SkipToTheEndpoint/7bf177daea583cb61720c96c7c2f55a8 to your computer and use it in GitHub Desktop.
<#
.SYNOPSIS
Installs Microsoft Managed Desktop Client Library and Microsoft Cloud Managed Desktop Extension
.EXAMPLE
.\SetupAutopatchClientPackage.ps1
#>
param(
[switch] $DisableTranscript = $false
)
# Ensures that Invoke-WebRequest uses TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Version and Location Parameters
$ClientLibraryVersion = 202206031
$CloudManagementExtensionVersion = '1.2.01996.78'
$cabDownloadUri = 'https://mmdcustomer.microsoft.com/clientsetup/20220621.1/AutopatchSetupPackage.cab'
$cabDownloadUriBackup = 'https://mmdcustomer.azureedge.net/clientsetup/20220621.1/AutopatchSetupPackage.cab'
function Set-Registry {
<#
.Synopsis
Modify registry String value.
.Description
Function to Modify registry String value.
.Parameter
-Hive 'HKLM' -Path '\Software\Policies\Microsoft\Windows\System' -Name 'CleanupProfiles' -Value 'Test' -Type 'String'
.Example
Set-Registry -Hive 'HKLM' -Path '\Software\Policies\Microsoft\Windows\System' -Name 'CleanupProfiles' -Value 'Test' -Type 'String'
.Inputs
string.
.Outputs
None.
#>
param (
[parameter(mandatory)] [string] $Hive,
[parameter(mandatory)] [string] $Path,
[parameter(mandatory)] [string] $Name,
[parameter(mandatory)] [string] $Value,
[parameter(mandatory)] [string] $Type
)
$regPath = ('{0}:\{1}' -f $Hive, $Path)
try {
if (!(Test-Path -Path $regPath)) {
$null = New-Item -Path $regPath -Force
}
Write-Host "[Set-Registry] Attempting to set registry value $Name at registry path $regpath and registry data $Value"
New-ItemProperty -Path $regPath -Name $Name -Value $Value -PropertyType $Type -Force -ErrorAction Continue
}
catch [System.InvalidOperationException] {
Write-Error $_.Exception.Message`n
Write-Host "[Set-Registry] Unable to create registry value $Name at registry path $regPath registry data $Value"
}
catch {
Write-Error $_.Exception.Message`n
}
}
function Get-Registry {
<#
.Synopsis
Get registry value.
.Description
Function to obtain registry value.
.Parameter
-Hive 'HKLM' -Path '\Software\Policies\Microsoft\Windows\System' -Name 'CleanupProfiles'
.Example
Get-Registry -Hive 'HKLM' -Path '\Software\Policies\Microsoft\Windows\System' -Name 'CleanupProfiles'
.Inputs
string
.Outputs
string
#>
param (
[parameter(mandatory)] [string] $Hive,
[parameter(mandatory)] [string] $Path,
[parameter(mandatory)] [string] $Name
)
$regPath = '{0}:\{1}' -f $Hive, $Path
$regValues = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
if (!$regValues)
{
return $null
}
else
{
return $regValues.$Name
}
}
function VerifyMicrosoftFileSignature {
<#
.Synopsis
Verifies if a file is Microsoft Signed
.Description
Checks if a file is signed by a certificate with a microsoft subject and rooted to the microsoft root
.Parameter
-FilePath
.Example
VerifyMicrosoftFileSignature -FilePath 'C:\temp\temp.cab'
.Inputs
string.
.Outputs
None.
#>
param (
[parameter(mandatory)] [string] $FilePath
)
$signature = Get-AuthenticodeSignature -FilePath $FilePath
Write-Host 'Digital signature validity:' $signature.status
Write-Host 'Signer Certificate subject:' $signature.signerCertificate.subject
$cert = $signature.SignerCertificate
$chain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain
$chain.Build($cert)
$rootCertSubject = ($chain.ChainElements | Select-Object -Last 1).Certificate.Subject
Write-Host 'Root Certificate subject:' $rootCertSubject
Write-Host 'Chain Status:' ($chain.ChainStatus | Format-List)
if ($chain.ChainStatus)
{
foreach ($chainStatus in $chain.ChainStatus)
{
# We are only okay with chain statuses that are NoError(0) or NotTimeValid(1)
if (($chainStatus.Status -eq 0) -or ($chainStatus.Status -eq 1))
{
continue
}
else
{
throw "Chain Not Trusted. Chain Status: $chainStatus.Status"
}
}
}
# Check that the signing cert is syntactically valid, has the Microsoft subject, is rooted to the Microsoft root cert, and has no errors in the chain
if (-not ($signature.status -eq 'valid' -and ($signature.signerCertificate.subject -eq 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' -and ($rootCertSubject -eq 'CN=Microsoft Root Certificate Authority 2011, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'))))
{
throw "Not signed by Microsoft PRS Cert with Microsoft Root"
}
}
function InstallClientLibrary
{
if ($CabClientLibraryVersion -ne $CurrentClientLibraryVersion)
{
Write-Host "Installing Client Library Version $CabClientLibraryVersion"
Write-Host 'Creating Client Library temp directory'
$ClientLibraryTempPath = "$ExpandedCabDirectory\ClientLibrary"
New-Item -Force -ItemType directory -Path "$ExpandedCabDirectory\ClientLibrary"
Expand-Archive -LiteralPath "$ExpandedCabDirectory\MmdBroker.zip" -DestinationPath $ClientLibraryTempPath
$ClientLibraryInstallScript = "$ClientLibraryTempPath\InstallMmdBroker.ps1"
& $ClientLibraryInstallScript -DisableTranscript $true
Write-Host "Exit code from installing Client Library: $LASTEXITCODE"
}
else
{
Write-Host 'Skipping Client Library Installation'
}
}
function InstallManagementExtension
{
if ($CabManagementExtensionVersion -ne $CurrentManagementExtensionVersion)
{
$CmdAgentLogPath = "$LogPath\AutopatchCloudManagedDesktopExtensionInstall" + $stampDate.ToFileTimeUtc() + '.log'
$ManagementExtensionMsi = "$ExpandedCabDirectory\cmdextension.msi"
Write-Host "Installing Management Extension Version $CabManagementExtensionVersion"
try
{
VerifyMicrosoftFileSignature -FilePath $ManagementExtensionMsi
}
catch
{
Write-Host 'Management Extension MSI is not trusted. Skipping'
return
}
# Attempt to save certain values for reinstall scenarios
$IoTDeviceId = Get-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'IoTDeviceId'
$IoTHostName = Get-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'IoTHostName'
$DeviceRegistrationType = Get-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'DeviceRegistrationType'
$Partners = Get-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'Partners'
$PartnersNextSyncTime = Get-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'PartnersNextSyncTime'
$ClientAgentNames = @(
"Microsoft CMD Client Agent"
"Windows Client Agent",
"Microsoft Cloud Managed Desktop Extension"
)
foreach ($AgentName in $ClientAgentNames){
$agent = Get-WmiObject -class Win32_Product -Filter "Name=`'$($AgentName)`'"
if ($agent) {
$productCode = $agent.IdentifyingNumber
$currentVersion = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$productCode" -Name "DisplayVersion"
$MSIUninstallArguments = @(
"/x"
$productCode
"/q"
"/norestart"
)
Start-Process "msiexec.exe" -ArgumentList $MSIUninstallArguments -Wait -NoNewWindow
Write-Host "Finished uninstall command line with param $($MSIUninstallArguments) for agent `'$($AgentName)`' with version $($currentVersion)"
}
}
# Restore Reg Values for Upgrade Scenario
if ($IoTDeviceId)
{
Write-Host "Rewriting IoTDeviceId: $IoTDeviceId"
Set-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'IoTDeviceId' -Value $IoTDeviceId -Type 'String'
}
if ($IoTHostName)
{
Write-Host "Rewriting IoTHostName: $IoTHostName"
Set-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'IoTHostName' -Value $IoTHostName -Type 'String'
}
if ($DeviceRegistrationType)
{
Write-Host "Rewriting DeviceRegistrationType: $DeviceRegistrationType"
Set-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'DeviceRegistrationType' -Value $DeviceRegistrationType -Type 'String'
}
if ($Partners)
{
Write-Host "Rewriting Partners: $Partners"
Set-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'Partners' -Value $Partners -Type 'String'
}
if ($PartnersNextSyncTime)
{
Write-Host "Rewriting PartnersNextSyncTime: $PartnersNextSyncTime"
Set-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\Settings" -Name 'PartnersNextSyncTime' -Value $PartnersNextSyncTime -Type 'String'
}
$MSIInstallArguments = @(
"/i"
"$ManagementExtensionMsi"
"SVCENV=$CabManagementExtensionEnvironment"
"/qb!"
"/l*v $CmdAgentLogPath"
"/norestart"
)
Start-Process "msiexec.exe" -ArgumentList $MSIInstallArguments -Wait -NoNewWindow
}
else
{
Write-Host 'Skipping Management Extension Installation'
}
}
function Cleanup {
<#
.Synopsis
Cleans up files, directories, and scheduled tasks associated with this install process except this script
.Description
Cleans up files, directories, and scheduled tasks associated with this install process except this script
.Parameter
-SuccessfulRun
.Example
Cleanup -SuccessfulRun $true
.Inputs
boolean.
.Outputs
None.
#>
param (
[boolean] $SuccessfulRun = $false
)
cd $PSScriptRoot
if (Test-Path $CabDirectory)
{
Write-Host 'Removing Cab directory'
Remove-Item -LiteralPath $CabDirectory -Force -Recurse
}
if ($SuccessfulRun)
{
# Cleanup scheduled task if it exists
Unregister-ScheduledTask -TaskName 'Autopatch Client Setup Installer' -Confirm:$false -ErrorAction SilentlyContinue
Write-Host "[SUCCESSFUL_AUTOPATCH_CLIENT_SETUP]"
}
else
{
Write-Host "[FAILED_AUTOPATCH_CLIENT_SETUP]"
}
if (-not $DisableTranscript)
{
# Stop Logging
Stop-Transcript -Verbose
}
}
# If Powershell is running the 32-bit version on a 64-bit machine, we need to force powershell to run in
# 64-bit mode.
if ($env:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
if ($myInvocation.Line) {
&"$env:WINDIR\sysnative\windowspowershell\v1.0\powershell.exe" -NonInteractive -NoProfile $myInvocation.Line
}
else
{
&"$env:WINDIR\sysnative\windowspowershell\v1.0\powershell.exe" -NonInteractive -NoProfile -file "$($myInvocation.InvocationName)" $args
}
exit $lastexitcode
}
$CabDirectory = "$env:SystemDrive\ProgramData\Microsoft\AutopatchSetup"
$LogPath = "$Env:windir\ccm\logs"
$LogPrefix = "$LogPath\AutopatchClientSetupInstallTask"
$stampDate = Get-Date
# Start Logging
if (-not $DisableTranscript)
{
$logFile = $LogPrefix + $stampDate.ToFileTimeUtc() + '.log'
Start-Transcript -Path $logFile
}
# Remove Autopatch Install logs that are 7+ days old
$cutOffDate = (Get-Date).AddDays(-7).ToFileTimeUtc()
foreach ($logFile in Get-ChildItem $LogPath) {
if ($logFile.Name.StartsWith($LogPrefix) -and $logFile.Extension.Equals('.log')) {
$logDateStr = $logFile.Name.Substring($LogPrefix.Length)
$logDateStr = $logDateStr.Substring(0, $logDateStr.IndexOf('.'))
$logDate = 0
if ([uint64]::TryParse($logDateStr, [ref]$logDate) -and $logDate -lt $cutOffDate) {
Remove-Item ('{0}{1}' -f $LogPath, $logFile)
Write-Host "Removed old log file $logFile"
}
}
}
$MmdRegistryKey = '\Software\Microsoft\MMD'
$CmdRegistryKey = 'Software\Microsoft\CloudManagementDesktop\Extension'
#Check Installed versions for Client Library and Management Extension
$CurrentClientLibraryVersion = Get-Registry -Hive 'HKLM' -Path "$MmdRegistryKey\Broker" -Name 'Version'
$CurrentManagementExtensionVersion = Get-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\ProductInfo" -Name 'Version'
Write-Host "Installed Client Library Version: $CurrentClientLibraryVersion; Installed Management Extension Verison: $CurrentManagementExtensionVersion"
if (($ClientLibraryVersion -eq $CurrentClientLibraryVersion) -and ($CloudManagementExtensionVersion -eq $CurrentManagementExtensionVersion))
{
Write-Host 'Client Library and Management Extension are up-to-date. Exiting Installation'
Cleanup -SuccessfulRun $true
exit 0
}
$ExpandedCabDirectory = "$env:SystemDrive\ProgramData\Microsoft\AutopatchSetup\Files"
$CabFile = "$CabDirectory\AutopatchSetupPackage.cab"
if (Test-Path $CabDirectory)
{
Write-Host 'Removing old Cab directory'
Remove-Item -LiteralPath $CabDirectory -Force -Recurse
}
Write-Host 'Creating Cab directory'
New-Item -Force -ItemType directory -Path $CabDirectory
Write-Host "Downloading Cab from $cabDownloadUri"
try
{
Invoke-WebRequest -URI $cabDownloadUri -OutFile $CabFile
}
catch
{
Write-Host $_.Exception.Message`n
Write-Host "Failed to download cab from $cabDownloadUri"
}
if (-not (Test-Path $CabFile -PathType Leaf))
{
Write-Host "Downloading Cab from $cabDownloadUriBackup"
try
{
Invoke-WebRequest -URI $cabDownloadUriBackup -OutFile $CabFile
}
catch
{
Write-Host $_.Exception.Message`n
Write-Host "Failed to download cab from $cabDownloadUriBackup"
Cleanup
exit 2
}
}
#Verify microsoft signature on cab
try
{
VerifyMicrosoftFileSignature -FilePath $CabFile
}
catch
{
Write-Host 'Cab is not trusted. Exiting'
Cleanup
exit 3
}
Write-Host 'Creating expanded Cab directory'
New-Item -Force -ItemType directory -Path $ExpandedCabDirectory
Write-Host 'Extract cab'
expand.exe $CabFile -F:* $ExpandedCabDirectory
Write-Host 'Parse config file'
$ConfigFileName = "$ExpandedCabDirectory\config.json"
$configProperties = @{}
$json = Get-Content $ConfigFileName | Out-String
(ConvertFrom-Json $json).psobject.properties | Foreach { $configProperties[$_.Name] = $_.Value }
$CabManagementExtensionEnvironment = $configProperties['ManagementExtensionEnvironment']
$CabClientLibraryVersion = $configProperties['ClientLibraryVersion']
$CabManagementExtensionVersion = $configProperties['ManagementExtensionVersion']
Write-Host "Cab Client Library Version: $CabClientLibraryVersion; Cab Management Extension Version: $CabManagementExtensionVersion"
InstallClientLibrary
InstallManagementExtension
# Verify Installations
$NewClientLibraryVersion = Get-Registry -Hive 'HKLM' -Path "$MmdRegistryKey\Broker" -Name 'Version'
$NewManagementExtensionVersion = Get-Registry -Hive 'HKLM' -Path "$CmdRegistryKey\ProductInfo" -Name 'Version'
if (($ClientLibraryVersion -eq $NewClientLibraryVersion) -and ($CloudManagementExtensionVersion -eq $NewManagementExtensionVersion))
{
Write-Host 'Client Library and Management Extension have installed successfully. Exiting Installation'
# Setting Autopatch registry value
Set-Registry -Hive 'HKLM' -Path "$MmdRegistryKey\Broker" -Name 'Autopatch' -Value '1' -Type 'Dword'
Cleanup -SuccessfulRun $true
exit 0
}
else
{
Write-Host 'Client Library and Management Extension have not installed successfully.'
Write-Host "Expected Client Library Version: $ClientLibraryVersion; Installed Client Library Version: $NewClientLibraryVersion."
Write-Host "Expected Management Extension Version: $CabManagementExtensionVersion; Installed Management Extension Version: $NewManagementExtensionVersion."
Cleanup
exit 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment