One issue that I have noticed since I have been testing the latest Windows 11 upgrade, is the automatic installation of the Copilot Progressive Web App (PWA).
The problem with this Copilot Progressive Web App is that it is intended for non-work accounts which makes it pointless on an Enterprise OS. To address this issue, I wrote the following PowerShell script to automate the process of preventing the Copilot PWA from being installed after a Windows 11 upgrade. By running this script before the user gets to their desktop, administrators can ensure that the Copilot PWA is not installed on any user profiles, providing a cleaner and more controlled environment.
NOTE – The script needs to run before the application is installed on any system since it is setting a few registry keys that Microsoft is using as a flag to detect if the application was installed.
This same script has also worked well on Windows 10 since we started noticing a Copilot app appear on some of our Windows 10 desktops. It can run after a Windows 11 upgrade is complete using a Success.cmd file to execute the script as soon as the upgrade is successful (I won’t go into detail on how to set this up since Microsoft has done a great job with documenting this process Run custom actions during a feature update | Microsoft Learn). Last but not least, you can add the script to your Autopilot process or OSD task sequence as well so new systems will not receive the application.
Powershell Script:
Function Write-Log {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True, ValueFromPipeline = $True,ValueFromPipelinebyPropertyName = $True)]
$Message,
$Component
)
Process {
# Populate the variables to log
$Time = (Get-Date -Format HH:mm:ss) + ".000+000"
$Date = Get-Date -Format MM-dd-yyyy
$TempMsg = "<![LOG[$Message]LOG]!><time=""$Time"" date=""$Date"" component=""$Component"" context="""" type="""" thread="""" file=""$Component"">"
# Create the component log entry
Write-Output $TempMsg | Out-File -FilePath $LogFile -Encoding "Default" -Append
}
}
# Prep logging
$Logfile = "$env:ProgramData\Logs\Software\CopilotRemoval.log"
If((Test-Path "$env:ProgramData\Logs\Software" ) -eq $False) { New-Item "$env:ProgramData\Logs\Software" -ItemType Directory -Force }
$SystemProfiles = 'S-1-5-18', 'S-1-5-19', 'S-1-5-20'
$UserProfileRegistryKey = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
$UserProfilesDirectory = Get-ItemProperty -LiteralPath $UserProfileRegistryKey -Name 'ProfilesDirectory' -ErrorAction 'Stop' | Select-Object -ExpandProperty 'ProfilesDirectory'
$DefaultProfileDirectory = Get-ItemProperty -LiteralPath $UserProfileRegistryKey -Name 'Default' -ErrorAction 'Stop' | Select-Object -ExpandProperty 'Default'
[array]$UserProfiles = @(Get-ChildItem -LiteralPath $UserProfileRegistryKey -ErrorAction 'Stop' | ForEach-Object {
Get-ItemProperty -LiteralPath $_.PSPath -ErrorAction 'Stop' | Where-Object { $null -ne $_.ProfileImagePath -and $SystemProfiles -notcontains $_.PSChildName } |
Select-Object @{ Label = 'SID'; Expression = { $_.PSChildName } }, @{ Label = 'ProfilePath'; Expression = { $_.ProfileImagePath } }
})
$UserProfiles = $UserProfiles | Where-Object { $_ -like "*\Users\*" }
$UserProfiles += New-Object -TypeName PSObject -Property @{SID="NA"; ProfilePath="$DefaultProfileDirectory" }
ForEach ($script:UserProfile in $UserProfiles) {
$UserName = $UserProfile.ProfilePath.Replace("$UserProfilesDirectory\","")
Write-Log "<====== Prepping $UserName to remove Copilot PWA ======>"
Write-Log "STEP 1 - Attempting to load $UserName's HKCU hive"
# Expected user registry key if user is logged in
$HKEY_UserPath = "Registry::HKEY_USERS\$($UserProfile.SID)"
# Create the path to the user's ntuser.dat
$NTUserPath = $UserProfile.ProfilePath + '\' + 'NTUSER.DAT'
# Load ntuser.dat file if user is not logged in (should be expected behavior during the in-place upgrade)
If((Test-Path -LiteralPath $HKEY_UserPath) -eq $False -or $($UserProfile.SID) -eq "N/A") {
# Check that the ntuser.dat path is valid
If(Test-Path $NTUserPath) {
# Attempt to load the user's registry
Try {
Write-Log "Loading $NTUserPath"
Start-Process "$env:windir\System32\reg.exe" -ArgumentList "load `"HKEY_USERS\$($UserProfile.SID)`" `"$NTUserPath`"" -PassThru -NoNewWindow -Wait
}
Catch {
Write-Log "Failed to load $NTUserPath !"
Write-Log "Failed Message: $($_.Exception.Message)"
Write-Log "Failed in Line Number: $($_.InvocationInfo.ScriptLineNumber)"
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
Start-Sleep -Seconds 5
Write-Log "Unloading $NTUserPath"
Start-Process "$env:windir\System32\reg.exe" -ArgumentList "unload `"HKEY_USERS\$($UserProfile.SID)`"" -PassThru -NoNewWindow -Wait
Return
}
# Adding Custom Settings
Write-Log "STEP 2 - Configure Custom Settings"
# Disable Copilot PWA
Try {
Write-Log "Prevent Copilot PWA from installing"
If(!(Test-Path -LiteralPath "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs")) {
New-Item "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs" -Force
}
New-ItemProperty -LiteralPath "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs" -Name 'CopilotPWAPreinstallRetryCount' -Value 1 -PropertyType DWord -Force -ErrorAction Stop
New-ItemProperty -LiteralPath "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs" -Name 'CopilotPWAPreinstallCompleted' -Value 1 -PropertyType DWord -Force -ErrorAction Stop
New-ItemProperty -LiteralPath "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs" -Name 'Microsoft.Copilot_8wekyb3d8bbwe' -Value 1 -PropertyType DWord -Force -ErrorAction Stop
}
Catch {
Write-Log "Failed to prevent Copilot PWA from installing"
}
# Attempt to unload the user's registry
Try {
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
Start-Sleep -Seconds 5
Write-Log "Unloading $NTUserPath"
Start-Process "$env:windir\System32\reg.exe" -ArgumentList "unload `"HKEY_USERS\$($UserProfile.SID)`"" -PassThru -NoNewWindow -Wait
}
Catch {
Write-Log "Failed to unload $NTUserPath !"
Write-Log "Failed Message: $($_.Exception.Message)"
Write-Log "Failed in Line Number: $($_.InvocationInfo.ScriptLineNumber)"
Continue
}
}
}
Else {
# Take care of profiles that are currently loaded and logged in
Write-Log "$UserName is logged in! Will skip loading ntuser.dat and make changes directly in HKEY_USERS\$($UserProfile.SID)"
# Adding additional Windows 11 customizations
Write-Log "STEP 2 - Configure Custom Settings"
# Disable Copilot PWA
Try {
Write-Log "Prevent Copilot PWA from installing"
If(!(Test-Path -LiteralPath "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs")) {
New-Item "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs" -Force
}
New-ItemProperty -LiteralPath "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs" -Name 'CopilotPWAPreinstallRetryCount' -Value 1 -PropertyType DWord -Force -ErrorAction Stop
New-ItemProperty -LiteralPath "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs" -Name 'CopilotPWAPreinstallCompleted' -Value 1 -PropertyType DWord -Force -ErrorAction Stop
New-ItemProperty -LiteralPath "Registry::HKEY_USERS\$($UserProfile.SID)\Software\Microsoft\Windows\CurrentVersion\Explorer\AutoInstalledPWAs" -Name 'Microsoft.Copilot_8wekyb3d8bbwe' -Value 1 -PropertyType DWord -Force -ErrorAction Stop
}
Catch {
Write-Log "Failed to prevent Copilot PWA from installing"
}
}
Write-Log "<====== $UserName has been updated successfully ======>"
}