This morning I saw a cool post from Gary Blok about automatically capturing hard blockers in a Windows 10 In-Place Upgrade task sequence. It inspired me to look a bit further at that, and I came up with the following PowerShell code which will search all the compatibility xml files created by Windows 10 setup and look for any hard blockers. These will then be reported either in the console, or you can write them to file where you can copy them to a central location together with your SetupDiag files, or you could stamp the info to the registry or a task sequence variable as Gary describes in his blog post. You could also simply run the script against an online remote computer using Invoke-Command.
The script is not the one-liner that Gary likes, so to use in a task sequence you’ll need to wrap it in a package and call it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Searches the Windows 10 Setup Compatibility logs for upgrade hard blockers | |
# Find all the compatibility xml files | |
$SearchLocation = 'C:\$WINDOWS.~BT\Sources\Panther' | |
$CompatibilityXMLs = Get-childitem "$SearchLocation\compat*.xml" | Sort LastWriteTime –Descending | |
# Create an array to hold the results | |
$Blockers = @() | |
# Search each file for any hard blockers | |
Foreach ($item in $CompatibilityXMLs) | |
{ | |
$xml = [xml]::new() | |
$xml.Load($item) | |
$HardBlocks = $xml.CompatReport.Hardware.HardwareItem | Where {$_.InnerXml -match 'BlockingType="Hard"'} | |
If($HardBlocks) | |
{ | |
Foreach ($HardBlock in $HardBlocks) | |
{ | |
$FileAge = (Get-Date).ToUniversalTime() – $item.LastWriteTimeUTC | |
$Blockers += [pscustomobject]@{ | |
ComputerName = $env:COMPUTERNAME | |
FileName = $item.Name | |
LastWriteTimeUTC = $item.LastWriteTimeUTC | |
FileAge = "$($Fileage.Days) days $($Fileage.hours) hours $($fileage.minutes) minutes" | |
BlockingType = $HardBlock.CompatibilityInfo.BlockingType | |
Title = $HardBlock.CompatibilityInfo.Title | |
Message = $HardBlock.CompatibilityInfo.Message | |
} | |
} | |
} | |
} | |
# Report results | |
If ($Blockers) | |
{ | |
$Blockers | |
# Export to file | |
#$Blockers | export-csv -Path "$env:SystemDrive\Windows\CCM\Logs\W10UpgradeHardBlockers.csv" -NoTypeInformation -UseCulture -Force | |
} | |
Else | |
{ | |
Write-host "No hard blockers found" | |
} |
The console output looks like this:
You should remove the FileAge property if using it in a task sequence as that’s a real-time value and is a quick indicator of when the blocker was reported.
If you use my solution here for improving the user experience in an IPU, you could also report this info to the end user by adding a script using my New-WPFMessageBox function, something like this…
$Stack = New-Object System.Windows.Controls.StackPanel $Stack.Orientation = "Vertical" $TextBox = New-Object System.Windows.Controls.TextBox $TextBox.BorderThickness = 0 $TextBox.Margin = 5 $TextBox.FontSize = 14 $TextBox.FontWeight = "Bold" $TextBox.Text = "The following hard blocks were found that prevent Windows 10 from upgrading:" $Stack.AddChild($TextBox) Foreach ($Blocker in $Blockers) { $TextBox = New-Object System.Windows.Controls.TextBox $TextBox.BorderThickness = 0 $TextBox.Margin = 5 $TextBox.FontSize = 14 $TextBox.Foreground = "Blue" $TextBox.Text = "$($Blocker.Title): $($Blocker.Message)" $Stack.AddChild($TextBox) } $TextBox = New-Object System.Windows.Controls.TextBox $TextBox.BorderThickness = 0 $TextBox.Margin = 5 $TextBox.FontSize = 14 $TextBox.Text = "Please contact the Helpdesk for assistance with this issue." $Stack.AddChild($TextBox) New-WPFMessageBox -Title "Windows 10 Upgrade Hard Block" -Content $Stack -TitleBackground Red -TitleTextForeground White -TitleFontSize 18
…which creates a message box like this:
Thanks to Gary and Keith Garner for the inspiration here!
Trevor, have you used this in an IPU? I’m trying to implement it but I’m not getting the message box to show up when called through Invoke-PSScriptAsUser.ps1. But if I run in by itself it works with no problem (have a task sequence that’s using ServiceUI to call Powershell ISE so that I can debug).
I made a few changes to try to debug and made just a message like you use for Show-CompatScanFailNotification.ps1 and it does show when called via Invoke-PSScriptAsUser.ps1
So this:
Doesn’t display when called through Invoke-PSScriptAsUser.ps1:
New-WPFMessageBox -Title “Windows 10 Upgrade Hard Block” -Content $Stack -TitleBackground Red -TitleTextForeground White -TitleFontSize 18
Does display when called through Invoke-PSScriptAsUser.ps1
New-WPFMessageBox -Content $Message -ContentTextForeground Black -ContentFontWeight DemiBold -ContentFontSize 14 -Title “Windows 10 Upgrade Hard Block” -TitleFontSize 24 -TitleBackground Red -TitleTextForeground White -Sound ‘Windows Exclamation’
Any ideas?
Hi Mike, try adding this line at the beginning of the script:
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
When running WPF from a PS console you must do this first – it isn’t necessary in the ISE.
Thank you. That fixed it.
Hi Trevor,
Great script, has really helped me with my IPU’s.
I’ve made some modifications to include drivers that are blocking the IPU and remove them. Hopefully it’s useful 😉
See below:
————————————————————————————————————
# Searches the Windows 10 Setup Compatibility logs for upgrade hard blockers
# Find all the compatibility xml files
$SearchLocation = ‘C:\$WINDOWS.~BT\Sources\Panther’
$CompatibilityXMLs = Get-childitem “$SearchLocation\compat*.xml” | Sort LastWriteTime -Descending
# Create an array to hold the results
$HardBlockers = @()
$SoftBlockers = @()
# Search each file for any hard blockers
Foreach ($item in $CompatibilityXMLs)
{
[xml]$xml = Get-Content $item
$HardBlocks = $xml.CompatReport.Hardware.HardwareItem | Where {$_.InnerXml -match ‘BlockingType=”Hard”‘}
If($HardBlocks)
{
Foreach ($HardBlock in $HardBlocks)
{
$FileAge = (Get-Date).ToUniversalTime() – $item.LastWriteTimeUTC
$HardwareBlockers += [pscustomobject]@{
ComputerName = $env:COMPUTERNAME
FileName = $item.Name
LastWriteTimeUTC = $item.LastWriteTimeUTC
FileAge = “$($Fileage.Days) days $($Fileage.hours) hours $($fileage.minutes) minutes”
BlockingType = $HardBlock.CompatibilityInfo.BlockingType
Title = $HardBlock.CompatibilityInfo.Title
Message = $HardBlock.CompatibilityInfo.Message
}
}
}
$SoftBlocks = $xml.CompatReport.DriverPackages.DriverPackage | Where {$_.BlockMigration -eq ‘True’}
If($SoftBlocks)
{
Foreach ($SoftBlock in $SoftBlocks)
{
$FileAge = (Get-Date).ToUniversalTime() – $item.LastWriteTimeUTC
$SoftBlockers += [pscustomobject]@{
ComputerName = $env:COMPUTERNAME
FileName = $item.Name
LastWriteTimeUTC = $item.LastWriteTimeUTC
FileAge = “$($Fileage.Days) days $($Fileage.hours) hours $($fileage.minutes) minutes”
BlockingType = $SoftBlock.BlockMigration
Driver = $SoftBlock.Inf
Signed = $SoftBlock.HasSignedBinaries
}
}
}
}
# Report results
If ($HardBlockers)
{
$HardBlockers
# Export to file
$HardBlockers | export-csv -Path “$SearchLocation\W10UpgradeHardBlockers.csv” -NoTypeInformation -UseCulture -Force
}
Else
{
Write-host “No hardware blockers found”
}
# Report results
If ($SoftBlockers)
{
$SoftBlockers
# Export to file
$SoftBlockers | export-csv -Path “$SearchLocation\W10UpgradeSoftBlockers.csv” -NoTypeInformation -UseCulture -Force
Foreach($driver in $SoftBlockers)
{
$uninst = $driver.driver
Start-Process -FilePath “$env:WINDIR\System32\pnputil.exe” -ArgumentList “/delete-driver $uninst /uninstall”
}
}
Else
{
Write-host “No software blockers found”
}
Thanks for that 🙂