0x80070643 – ERROR_INSTALL_FAILURE (Part 3)

  • Context

Reagentc.exe is far from perfect.

Here’s the horror show I encountered while patching cve-2024-20666

  • Case1: reagentc.exe refused to mount winre.wim
  • Case2: reagentc.exe refused to mount winre.wim
  • Case 3: Worse, reagentc.exe /disable moves the Winre.wim onto itself instead of moving it to a safe location C:\Windows\system32\Recovery\ and reports a success.

In this specific case, if you follow the Microsoft guidance and delete the partition, the WinRE.wim that was on the partition (instead of C:\Windows\system32\Recovery\) is lost.

You cannot re-enable it. You need to mount the install.wim from the installation media and extract it from the mounted install.wim image.

0x80070643 – ERROR_INSTALL_FAILURE (Part 2)

  • Context

Microsoft recently published a security update CVE-2024-20666 where manual steps may be required if you hit: 0x80070643 – ERROR_INSTALL_FAILURE.

We’ve seen in Part 1 what needs to be done manually when the recovery partition in use sits on the left of the operating system partition.

  • Problem

If the official guidance about disk sizing has been followed, the recovery partition (RE) where WinRE.wim is enabled is located on the right of the main operating system drive (C:).

For GPT based disk (guidance):

For BIOS based disk (guidance)

In this case, the recommended guidance KB5028997 published by Microsoft about how to resize the WinRE partition can be followed.

If you’ve hundreds or thousands of computers where you need to resize the recovery partition, how to automate the required resizing operation?

  • Solution

I’ve written a quick PowerShell module to ease the burden of

  1. identifying the WinRE location
  2. testing its available space (Microsoft recommends 250MB of free space)
  3. resizing the recovery partition on a GPT based disk

Let’s see how to use the functions of the module:

# Load the module
Import-Module ~/documents/WinRE.psm1 -Verbose
# Get reagentc.exe info as a list of PSCustomObject
Get-ReAgentCInfo
# Get the location of the WinRE.wim
Get-WinRELocation
# Get the recovery partition as a standard partition object
Get-WinREPartition
# Get more info (like the free space left of the partition)
Get-WinREPartitionExtendedInfo
# Test if there's a minimum of 250MB of free space
Test-WinREPartition
# if the function returns $true, it requires resizing, adding at least 250MB of free space (recommended by Microsoft)

# There's a parameter to test against another size if the 250MB recommendation changes in the future
# Resize the partition by adding 250MB of free space

# the function supports what if to show what it would do.
# it also includes some safeguards.
Resize-WinREPartition -WhatIf

Here’s what it looks like when it resizes the recovery partition sitting at the end of the disk:

Happy days, the Test-WinREPartition now reports that it has more than 250MB of free space 😎

Here’s the code of the module

Function Get-ReAgentCInfo {
<#
.SYNOPSIS
Get the Windows Recovery Partition (WinRE) info using reagentc.exe
.DESCRIPTION
Get the Windows Recovery Partition (WinRE) info using reagentc.exe
#>
[CmdletBinding()]
Param()
Begin {}
Process {}
End {
& (Get-Command "$($env:systemroot)\system32\reagentc.exe") @('/info') |
Select-Object -Skip 3 |
Select-Object -First 7|
ForEach-Object {
if ($_ -match ':\s') {
$n,$s=$_ -split ':'
[PSCustomObject]@{Name = $n.Trim() ; Value = $s.Trim()}
}
}
}
}
Function Get-WinRELocation {
<#
.SYNOPSIS
Get the Windows Recovery Partition (WinRE) location
.DESCRIPTION
Get the Windows Recovery Partition (WinRE) location
#>
[CmdletBinding()]
Param()
Begin {}
Process {}
End {
([regex]'\\\\\?\\GLOBALROOT\\device\\harddisk(?<Disk>\d{1})\\partition(?<Partition>\d{1})\\(?<Path>.+)').Matches(
(Get-ReAgentCInfo | Where-Object { $_.Name -match '^Windows\sRE\slocation' }).Value
) | Select-Object -ExpandProperty Groups | Select-Object -Last 3 | ForEach-Object {
@{ "$($_.Name)" = $_.Value }
}
}
}
Function Get-WinREPartition {
<#
.SYNOPSIS
Get the Windows Recovery Partition (WinRE)
.DESCRIPTION
Get the Windows Recovery Partition (WinRE)
#>
[CmdletBinding()]
Param()
Begin {}
Process {}
End {
if ($r = Get-WinRELocation) {
if ($r.Disk -match '\d{1}') {
if ($r.Partition -match '\d{1}') {
try {
Get-Partition -DiskNumber "$($r.Disk)" -PartitionNumber "$($r.Partition)" -ErrorAction Stop
} catch {
Write-Warning -Message "Failed to get partition because $($_.Exception.Message)"
}
} else {
Write-Warning -Message 'Partition returned from Get-WinRELocation is not a digit'
}
} else {
Write-Warning -Message 'Disk returned from Get-WinRELocation is not a digit'
}
}
}
}
Function Get-WinREPartitionExtendedInfo {
<#
.SYNOPSIS
Get extended info about the Windows Recovery Partition (WinRE)
.DESCRIPTION
Get extended info about the Windows Recovery Partition (WinRE)
#>
[CmdletBinding()]
Param()
Begin {}
Process {}
End {
if($winRE = Get-WinREPartition) {
[PSCustomObject]@{
Size = $winRE.Size
FreeSpace = ($winRE | Get-Volume).SizeRemaining
Type = $winRE.Type
}
}
}
}
Function Test-WinREPartition {
<#
.SYNOPSIS
Test if the Windows Recovery Partition is less than 250MB
.DESCRIPTION
Test if the Windows Recovery Partition is less than 250MB
.PARAMETER FreespaceMB
Specify the minimum freespace required in MB.
By default, 250MB are used.
#>
[OutputType([system.Boolean])]
[CmdletBinding()]
Param(
[Parameter()]
[ValidateRange(1048576,2146435072)]
[int32]$FreespaceMB = 250MB
)
Begin {}
Process {}
End {
if ($WinREInfo = Get-WinREPartitionExtendedInfo) {
if ($winREInfo.FreeSpace -lt $FreespaceMB) {
Write-Verbose -Message "WinRE freespace is less than $($FreespaceMB)"
$true
} else {
Write-Verbose -Message "WinRE freespace is more than $($FreespaceMB)"
$false
}
} else {
Write-Warning -Message 'Could not reliably get WinRE partition info'
$false
}
}
}
Function Resize-WinREPartition {
<#
.SYNOPSIS
Resize the Windows Recovery (WinRE) partition
.DESCRIPTION
Resize the Windows Recovery (WinRE) partition
.PARAMETER Size
Specify the size in MB that should be added to the partition.
By default 250MB will be added. A maximum of 2047MB can be specified.
.PARAMETER OperatingSystemPartitionLetter
Specify the letter of the operation system partition.
By default, the C driver letter is used
#>
[CmdletBinding(SupportsShouldProcess)]
Param(
[Parameter()]
[ValidateRange(1048576,2146435072)]
[int32]$Size = 250MB,
[Parameter()]
$OperatingSystemPartitionLetter = 'C'
)
Begin {
$ReAgentInfo = Get-ReAgentCInfo
$REPartInfo = Get-WinREPartition -WarningVariable w -WarningAction SilentlyContinue
$REExtInfo = Get-WinREPartitionExtendedInfo
}
Process {}
End {
if (Test-Path -Path 'R:\') {
Write-Warning -Message 'R drive letter is already in use, must be available, cannot continue'
return
}
if (-not($REPartInfo)) {
Write-Warning -Message 'Failed to obtain reliable WinRE info, cannot continue'
return
}
if ($REExtInfo.Type -ne 'Recovery' ) {
Write-Warning -Message 'GPT type required to continue'
return
}
if ($REPartInfo.PartitionNumber -eq 1) {
Write-Warning -Message 'WinRE is at the left of the OS partition, it requires adding a new one at the end of the disk'
return
}
$REDisabledOk = $RemovedPartOk = $osPartOk = $NewWinREPartOk = $REFolderOk = $RLetterOk = $false
# Step 1. Save WinRE.wim
if ($pscmdlet.ShouldProcess("$(($REPartInfo).PartitionNumber)",'Saving WinRE.wim from partition number')) {
try {
$HT = @{
DiskNumber = "$(($REPartInfo).DiskNumber)"
PartitionNumber = "$(($REPartInfo).PartitionNumber)"
ErrorAction = 'Stop'
}
Get-Partition @HT | Set-Partition -NewDriveLetter R -ErrorAction Stop
$RLetterOk = $true
Write-Verbose -Message 'Successfully assigned driver letter R to the recovery partition'
} catch {
Write-Warning -Message "Failed to assign letter R to the recovery partition because $($_.Exception.Message)"
}
if ($RLetterOk) {
$null = & (Get-Command "$($env:systemroot)\system32\robocopy.exe") @('R:\Recovery\WindowsRE',"$($env:temp)\WinRE.Safe",'WinRE.wim','/R:0','/Z')
if ($LASTEXITCODE -eq 1) {
# https://learn.microsoft.com/en-us/troubleshoot/windows-server/backup-and-storage/return-codes-used-robocopy-utility
Write-Verbose -Message 'Successfully copied WinRE.wim'
} else {
Write-Warning -Message 'Failed to copy WinRE.wim'
}
}
}
# Step 2. Disable WinRE
if ($pscmdlet.ShouldProcess("$(($REPartInfo).PartitionNumber)",'Disabling WinRE with reagentc.exe located on partition number')) {
$null = & (Get-Command "$($env:systemroot)\system32\reagentc.exe") @('/disable')
}
if ((Get-ReAgentCInfo | Where-Object { $_.Name -match '^Windows\sRE\sStatus' }).Value -eq 'Disabled') {
Write-Verbose -Message 'Successfully disabled WinRE'
$REDisabledOk = $true
} else {
if ($PSBoundParameters.ContainsKey('WhatIf')) {
$REDisabledOk = $true
} else {
Write-Warning -Message 'Failed to disable WinRE, cannot continue'
}
}
if ($REDisabledOk) {
# Step 3. Resize main OS drive
try {
$HT = @{
DriveLetter = "$($OperatingSystemPartitionLetter)"
Size = ((Get-Partition -DriveLetter $($OperatingSystemPartitionLetter)).Size – $Size)
ErrorAction = 'Stop'
}
if ($pscmdlet.ShouldProcess("$($OperatingSystemPartitionLetter)",'Resizing the volume')) {
$OSPart = Resize-Partition @HT -PassThru
Write-Verbose -Message "Successfully shrinked the volume $($OperatingSystemPartitionLetter):"
}
$OsPartOk = $true
} catch {
Write-Warning -Message "Failed to shrink $($OperatingSystemPartitionLetter): from $($Size) because $($_.Exception.Message)"
}
# Step 4. Remove WinRE part
if ($osPartOk) {
try {
$HT = @{
DiskNumber = "$(($REPartInfo).DiskNumber)"
PartitionNumber = "$(($REPartInfo).PartitionNumber)"
Confirm = $false
ErrorAction = 'Stop'
}
if ($pscmdlet.ShouldProcess("$(($REPartInfo).PartitionNumber)",'Removing WinRE partition number')) {
$RemovedPart = Remove-Partition @HT -PassThru
Write-Verbose -Message 'Successfully removed the WinRE partition'
}
$RemovedPartOk = $true
} catch {
Write-Warning -Message "Failed to remove the WinRE partition because $($_.Exception.Message)"
}
}
# Step 5. Recreate WinRE part
if ($RemovedPartOk) {
try {
$HT = @{
DiskNumber = "$(($REPartInfo).DiskNumber)"
UseMaximumSize = [switch]::Present
GptType = '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}'
IsHidden = [switch]::Present
ErrorAction = 'Stop'
}
if ($pscmdlet.ShouldProcess("$(($REPartInfo).PartitionNumber)",'Recreating WinRE partition number')) {
$NewWinREPart = New-Partition @HT
Write-Verbose -Message 'Successfully recreated the WinRE partition'
}
@'
sel disk {0}
sel part {1}
gpt attributes=0x8000000000000001
format quick fs=ntfs label='WinRE'
assign letter=R
'@ -f "$($REPartInfo.DiskNumber)","$($REPartInfo.PartitionNumber)" |
Out-File -FilePath "$($env:temp)\diskpartWinRE.txt" -Encoding ascii -ErrorAction Stop -Force
if ($pscmdlet.ShouldProcess("$(($REPartInfo).PartitionNumber)",'Setting GPT attributes on new WinRE partition number')) {
# https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/diskpart-scripts-and-examples
Start-Sleep -Seconds 15
$null = & (Get-Command "$($env:systemroot)\system32\diskpart.exe") @('/s',"$($env:temp)\diskpartWinRE.txt")
}
if ($LASTEXITCODE -eq 0) {
Write-Verbose -Message 'Successfully set GPT attributes on the WinRE partition'
$NewWinREPartOk = $true
} else {
if ($PSBoundParameters.ContainsKey('WhatIf')) {
$NewWinREPartOk = $true
} else {
Write-Warning -Message 'Failed to set the GPT attributes on the WinRE partition'
}
}
} catch {
Write-Warning -Message "Failed recreate the WinRE partition because $($_.Exception.Message)"
}
}
if ($NewWinREPartOk) {
# Step 6. Add back WinRE.wim
if ($pscmdlet.ShouldProcess("$(($REPartInfo).PartitionNumber)",'Adding back required file to partition number')) {
try {
$null = New-Item -Path 'R:\Recovery\WindowsRE' -ItemType Directory -Force -ErrorAction Stop
$REFolderOk = $true
} catch {
Write-Warning -Message "Failed to create R:\Recovery\WindowsRE folder because $($_.Exception.Message)"
}
if ($REFolderOk) {
$null = & (Get-Command "$($env:systemroot)\system32\robocopy.exe") @("$($env:temp)\WinRE.Safe",'R:\Recovery\WindowsRE','WinRE.wim','/R:0','/Z')
if ($LASTEXITCODE -eq 1) {
# https://learn.microsoft.com/en-us/troubleshoot/windows-server/backup-and-storage/return-codes-used-robocopy-utility
Write-Verbose -Message 'Successfully copied WinRE.wim'
} else {
Write-Warning -Message 'Failed to copy WinRE.wim'
}
}
}
# Step 7. Set back target to WinRE.wim
if ($pscmdlet.ShouldProcess("$(($REPartInfo).PartitionNumber)",'Set back target to WinRE.wim with reagentc.exe on partition number')) {
$null = & (Get-Command "$($env:systemroot)\system32\reagentc.exe") @('/setreimage','/path','R:\Recovery\WindowsRE\Winre.wim','/target','C:\Windows')
if ($LASTEXITCODE -eq 0) {
Write-Verbose -Message 'Successfully set back target to WinRE.wim'
} else {
Write-Warning -Message 'Failed to set back target to WinRE.wim'
}
}
# Step 8. Re-enable WinRE
if ($pscmdlet.ShouldProcess("$(($REPartInfo).PartitionNumber)",'Re-enabling WinRE with reagentc.exe using partition number')) {
$null = & (Get-Command "$($env:systemroot)\system32\reagentc.exe") @('/enable')
if ($LASTEXITCODE -eq 0) {
Write-Verbose -Message 'Successfully re-enabled WinRE'
} else {
Write-Warning -Message 'Failed to re-enable WinRE'
}
}
}
}
}
}
Export-ModuleMember -Function 'Get-ReAgentCInfo','Get-WinRELocation','Get-WinREPartition',
'Get-WinREPartitionExtendedInfo','Test-WinREPartition','Resize-WinREPartition'
view raw WinRE.psm1 hosted with ❤ by GitHub

0x80070643 – ERROR_INSTALL_FAILURE (Part 1)

  • Context

Microsoft recently published a security update CVE-2024-20666 where manual steps may be required if you hit: 0x80070643 – ERROR_INSTALL_FAILURE.

  • Problem

It’s the case if the partition where WinRE is located isn’t big enough.

2nd problem, the recommended guidance KB5028997 published by Microsoft about how to resize the WinRE partition cannot be followed blindly, especially if the WinRE partition sits on the left of the main operating system partition.

  • Solution

In this case, the shortest solution doesn’t consist in increasing the WinRE partition size.

The shortest solution is the following: having a new WinRE partition at the end of the disk, sitting on the right of the main operating system driver (C:)

Let’s do it:

reagentc.exe /info
reagentc.exe /disable
if (Get-Disk -Number 0 ).PartitionStyle -eq 'GPT') {
diskpart
sel disk 0
sel part 4
shrink desired=750 minimum=750
create partition primary id=de94bba4-06d1-4d40-a16a-bfd50179d6ac
gpt attributes =0x8000000000000001
format quick fs=ntfs label=WinRE
assign letter=R
exit
md R:\Recovery\WindowsRE
copy C:\Windows\system32\Recovery\Winre.wim R:\Recovery\WindowsRE
reagentc.exe /setreimage /path R:\Recovery\WindowsRE\Winre.wim /target C:\Windows
reagentc.exe /enable
}
reagentc.exe /info
usoclient.exe StartInteractiveScan
reagentc.exe /boottore
Restart-Computer

My WinRE.wim increased from 495MB to 597MB

After the reboot, the WU history displays:

and the diskmgmt.msc displays:

Internet Explorer message in event viewer: access denied

  • Context

After Microsoft has removed Internet Explorer 11 in June 2022, only IE-Mode (aka EMIE) remains.

For corporate devices with IE-mode, IE11 was not really fully removed.

  • Issue

So, when you launch the event viewer, you end up with these error messages

  • Solution

It appears that there’s a specific ACL deployed on this log that’s reponsible for this behaviour.

To get rid of this, you can delete the following registry value:

Remove-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Internet Explorer' -Name CustomSD

Quick Tip: Get CPU temperature

  • Context

I had a problem with my water cooling system on the desktop and I needed to check the temperature of the CPU.

I replaced the water cooling and wanted to get the CPU temperature directly in Windows w/o installing a third party software.

  • Issue

The task manager in Windows doesn’t display the CPU temperature but you can get the GPU temperature. Nice, but it’s not what I was looking for.

  • Solution

To get it in Celsius, you need to divide by 10 and substract 273. The original value is stored as tenths of Kelvin.

(Get-CimInstance -Namespace root/WMI -ClassName MSAcpi_ThermalZoneTemperature | 
Select-Object -Expand CurrentTemperature )/10 - 273.15

About creating VPN Always On user profile

  • Context

I want to deploy the VPN Always On user tunnel using a logonscript or a scheduled interactive task for non-admin users.

  • Problem

The following Microsoft official learning guides:
Tutorial: Deploy Always On VPN – Configure Always On VPN profile for Windows 10+ clients
Configure an Always On VPN user tunnel

… do provide a single method to do that. It uses the CreateInstance on the WMI class named MDM_VPNv2_01 under the root\cimv2\mdm\dmmap namespace.

Unfortunaletly, it throws the following error:

because it does require admin priviledges. It’s a dead end .

The other issue is that it’s broadly accepted to be redirected to be using inTune or Configuration Manager to deploy a VPN profile or other solutions.
Cannot provision Always On VPN profile to non-admin using Powershell #580
Deploying Always On VPN Profile to non-admin accounts #1820
Is it possible to deploy AOVPN user tunnel using GPO

  • Solution

Why not simply use the built-in Windows cmdlets provided by the VpnClient module?

Get-Command -Module VpnClient

Let’s say, you’ve the XML definition of the EAP configuration.
Here’s an example with contoso.com with 2 VPN servers, 2 DNS servers and suffixes and a class C private network routed through the tunnel where you just use the standard cmdlets provided:

$EAP = @'
    <EapHostConfig xmlns="http://www.microsoft.com/provisioning/EapHostConfig">
      <EapMethod>...</EapMethod>
      <Config xmlns="http://www.microsoft.com/provisioning/EapHostConfig">
      </Config>
    </EapHostConfig>
'@

$HT = @{ ErrorAction = 'Stop' }
try {
    $HTVPN = @{
        ServerAddress = 'vpn01.contoso.com'
        SplitTunneling = [switch]::Present
        DnsSuffix = 'contoso.com'
        TunnelType = 'Automatic'
        EncryptionLevel = 'Required'
        AuthenticationMethod = 'Eap'
        RememberCredential = [switch]::Present
        UseWinlogonCredential = $false
        EapConfigXmlStream = ([xml]$EAP)
        ServerList = ((New-VpnServerAddress -ServerAddress 'vpn01.contoso.com' -FriendlyName 'VPN01'),
                      (New-VpnServerAddress -ServerAddress 'vpn02.contoso.com' -FriendlyName 'VPN02'))
    }
    Add-VpnConnection -Name 'Corp VPN' @HTVPN @HT
    Add-VpnConnectionRoute -ConnectionName 'Corp VPN' -DestinationPrefix '192.168.0.0/16' -RouteMetric 1 @HT
    Add-VpnConnectionTriggerDnsConfiguration -ConnectionName 'Corp VPN' -DnsSuffix '.contoso.com' -DnsIPAddress '192.168.0.100','192.168.0.101' -Force  @HT
    Add-VpnConnectionTriggerDnsConfiguration -ConnectionName 'Corp VPN' -DnsSuffix '.sub.contoso.com' -DnsIPAddress '192.168.0.100','192.168.0.101' -Force  @HT
    Set-VpnConnectionTriggerDnsConfiguration -ConnectionName 'Corp VPN' -DnsSuffixSearchList 'contoso.com'  @HT
    Add-VpnConnectionTriggerTrustedNetwork -ConnectionName 'Corp VPN' -DnsSuffix 'contoso.com' -Force  @HT
} catch {
    Write-Warning -Message "Failed to create user tunnel because $($_.Exception.Message)"
}

Easy or not? 😎

Importing updates into WSUS

  • Context

Microsoft recently published an article about Importing updates into WSUS is changing and provided a PowerShell script:

The script is available on this page

  • Problem

Let’s start by a positive remark. There’s a Help block defined in the script that works.

Get-help C:\temp\ImportUpdateToWSUS.ps1

What’s wrong and what could go wrong?

The script isn’t digitally signed. You cannot verify its integrity and make sure it comes from Microsoft. You should trust the source and the copy button.

The script isn’t stored on the PowerShellGallery. The only delivery mechanism is the copy button in your browser.

The page says that you should create the script ImportUpdateToWSUS.ps1 into C:\temp. That’s ok if you’ve created a C:\temp folder and you don’t have any security solution in place that would block the execution from C:\temp. This location that doesn’t exist by default and may look suspicious. The page doesn’t mention the ExecutionPolicy inside PowerShell. Hopefully, yours isn’t set to Restricted or AllSigned.

Let’s quickly dig into the content and simply run the PSScriptAnalyzer module to get an overall felling.

Invoke-ScriptAnalyzer C:\temp\ImportUpdateToWSUS.ps1

The author(s) was/were lazy enough to avoid using Invoke-ScriptAnalyzer and get rid of these reported issues.

Do you know that you can get rid of the extra whitespaces by running this cmdlet?

Invoke-ScriptAnalyzer C:\temp\ImportUpdateToWSUS.ps1 -IncludeRule PSAvoidTrailingWhitespace -Fix -Verbose

Let’s come to the Write-Host issue reported. You can replace it with by Write-Verbose. You also need to get rid of the -NoNewLine after Write-Host because Write-Verbose isn’t painting the console, it sends the data to the PowerShell Verbose stream and it doesn’t have a -NoNewLine parameter.

How to fix the last offending PSAvoidUsingInvokeExpression?

Instead of storing the Get-WSUSServer as a string and appending its parameters and invoking it at the end using the dangerous Invoke-Expression cmdlet, the author could use an empty hashtable, append the parameters, and directly invoke Get-WSUSServer with splatting.

What else? There’s an attempt to handle errors but it’s missing the critical part. You should use explicitly an ErrorAction parameter set to Stop. Otherwise, the ErrorAction preference at that script scope applies. There isn’t any in the script, the one defined at the global scope applies. If it hasn’t been altered the default ErrorActionPreference is set by default to ‘Continue’. If that’s the case, the catch block defined in the script won’t be used.

I’m proposing a curated versioned of the original script on this page (you can explore revisions to see the above remarks implemented)

I may have other comments but I’ll stop here because it made my eyes bleed enough.

Explore a WSUS sync error

  • Context

I’ve a WSUS server in Core mode and there’s only a PowerShell console to manage it.

  • Problem

When I get a synchronisation that failed, I’ve got the following where…

..the error text is empty. 😦 You only know that it failed.

  • Solution

You can get the UpdateId using this command:

(Get-WsusServer).GetSubscription().GetLastSynchronizationInfo().UpdateErrors.UpdateId

You can get the ErrorText like this:

(Get-WsusServer).GetSubscription().GetLastSynchronizationInfo().UpdateErrors.ErrorText

In the above case, running a new synchronisation solved the issue.

CVE-2023-36884

  • Context

Microsoft has released a security bulletin for the zero day CVE-2023-36884 on this page and posted about the attack and workarounds on this blog.

If you applied security updates for Office, there’s no need to panic.

Customers who use Microsoft 365 Apps (Versions 2302 and later) are protected from exploitation of the vulnerability via Office.

  • Problem

If you cannot apply the security updates or if you want to get rid of the attack surface and don’t care too much about the impact of the hardening, you can implement what follows.

  • Solution

To create the mitigation proposed by Microsoft, I’ve created the following script that will add a group policy (GPO) with the required settings for the FEATURE_BLOCK_CROSS_PROTOCOL_FILE_NAVIGATION.

#Requires -RunasAdministrator
#Requires -Modules ActiveDirectory,GroupPolicy
# Make sure we can reach the PDC
$PDC = (Get-ADDomainController -Service 1 -Discover -ErrorAction SilentlyContinue).Hostname
if ($PDC) {
# Get the domain name
$DomainName = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
# Create the GPO
if (-not($GPO = Get-GPO -Name 'Emergency: M365 CVE-2023-36884' -Domain "$($DomainName)" -ErrorAction SilentlyContinue)){
try {
$GPO = New-GPO -Name 'Emergency: M365 CVE-2023-36884' -Domain "$($DomainName)" -ErrorAction Stop
} catch {
Write-Warning -Message "Failed to create the M365 CVE-2023-36884 GPO because $($_.Exception.Message)"
}
}
if ($GPO) {
# Don't need user settings
$GPO.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled
# Main hashtable
$HT = @{ GUID = ($GPO).Id ; ErrorAction = 'Stop' }
@(
'Excel.exe',
'Graph.exe',
'MSAccess.exe',
'MSPub.exe',
'Powerpnt.exe'
'Visio.exe',
'WinProj.exe',
'WinWord.exe',
'Wordpad.exe'
) |
ForEach-Object {
$reg = @{
Key = 'HKLM\SOFTWARE\Policies\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BLOCK_CROSS_PROTOCOL_FILE_NAVIGATION' ;
ValueName = $_ ; Type = 'DWORD' ; Value = 0x1
}
try {
Set-GPRegistryValue @HT @reg
} catch {
Write-Warning -Message "Faile to set GPO setting because $($_.Exception.Message)"
}
}
}
}

You end up with the following GPO settings:

What’s next? Just link the GPO in Active Directory where it makes sense to apply it to computers beneath.

When it’s applied, you’ve the following on a client computer:

Add a printer driver

  • Context

My colleague asked for a new printer driver that wasn’t built-in Windows.

  • Problem

Whenever I used both InfPath and Name parameters for the Add-PrinterDriver cmdlet, it throws an error:

  • Solution

Use the built-in executable pnputil.exe to first inject the driver in the catalog using its .inf file and then the Add-PrinterDriver cmdlet with its Name parameter only.

pnputil.exe  /add-driver "$($env:temp)\setup\Xerox_C310_C315_PCL6.inf"
Add-PrinterDriver -Verbose -Name "Xerox C315 Color MFP V4 PCL6"

B..ugly 😦