#requires -Version 5.1
<#
.SYNOPSIS
Removes and blocks the consumer Microsoft Copilot experience on a personal Windows PC.
.DESCRIPTION
Run this script from Windows PowerShell as a local administrator.
The script:
1. Removes installed Microsoft.Copilot Appx packages.
2. Removes provisioned Microsoft.Copilot packages for future user profiles.
3. Enables the Windows policy that requests removal of the consumer Copilot app.
4. Adds an AppLocker packaged-app deny rule for MICROSOFT.COPILOT.
5. Blocks consumer Copilot web entry points in Edge, Chrome, Firefox, and the hosts file.
This script targets consumer Microsoft Copilot. It does not intentionally block
Microsoft 365 Copilot used with a work or school account.
.PARAMETER Mode
Enforce applies the controls. Detect checks them without making changes.
.EXAMPLE
powershell.exe -ExecutionPolicy Bypass -File .\Block-Microsoft-Consumer-Copilot.ps1
.EXAMPLE
powershell.exe -ExecutionPolicy Bypass -File .\Block-Microsoft-Consumer-Copilot.ps1 -Mode Detect
#>
[CmdletBinding()]
param(
[ValidateSet('Enforce', 'Detect')]
[string]$Mode = 'Enforce'
)
$ErrorActionPreference = 'Stop'
$copilotPublisher = 'CN=MICROSOFT CORPORATION, O=MICROSOFT CORPORATION, L=REDMOND, S=WASHINGTON, C=US'
$copilotProduct = 'MICROSOFT.COPILOT'
$appLockerAllowRuleId = 'A54A1EF4-0C58-471C-8F31-4C8B5DB5B7A1'
$appLockerDenyRuleId = 'B8E9655C-E6E8-4E84-B5D7-7D89996E54A2'
$logDirectory = Join-Path $env:ProgramData 'CopilotBlock'
$logPath = Join-Path $logDirectory 'Block-Microsoft-Consumer-Copilot.log'
$blockedBrowserPatterns = @(
'*://copilot.microsoft.com/*',
'*://*.copilot.microsoft.com/*',
'*://www.bing.com/chat*',
'*://bing.com/chat*',
'*://www.bing.com/copilot*',
'*://bing.com/copilot*'
)
$blockedHostNames = @(
)
function Write-Log {
param([string]$Message)
if (-not (Test-Path -LiteralPath $logDirectory)) {
New-Item -ItemType Directory -Path $logDirectory -Force | Out-Null
}
$entry = '{0:u} {1}' -f (Get-Date), $Message
Write-Host $entry
Add-Content -LiteralPath $logPath -Value $entry -Encoding UTF8
}
function Test-IsAdministrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Remove-CopilotPackages {
Write-Log 'Removing installed Microsoft.Copilot packages.'
$installedPackages = @(Get-AppxPackage -AllUsers -Name 'Microsoft.Copilot' -ErrorAction SilentlyContinue)
foreach ($package in $installedPackages) {
try {
Remove-AppxPackage -Package $package.PackageFullName -AllUsers -ErrorAction Stop
Write-Log "Removed installed package: $($package.PackageFullName)"
}
catch {
Write-Log "All-users removal failed for $($package.PackageFullName); attempting current-user removal."
Remove-AppxPackage -Package $package.PackageFullName -ErrorAction SilentlyContinue
}
}
$provisionedPackages = @(
Get-AppxProvisionedPackage -Online |
Where-Object {
$_.DisplayName -eq 'Microsoft.Copilot' -or
$_.PackageName -like 'Microsoft.Copilot_*'
}
)
foreach ($package in $provisionedPackages) {
Remove-AppxProvisionedPackage -Online -PackageName $package.PackageName -AllUsers |
Out-Null
Write-Log "Removed provisioned package: $($package.PackageName)"
}
}
function Set-CopilotRemovalPolicy {
$policyPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI'
New-Item -Path $policyPath -Force | Out-Null
New-ItemProperty -Path $policyPath `
-Name 'RemoveMicrosoftCopilotApp' `
-PropertyType DWord `
-Value 1 `
-Force | Out-Null
Write-Log 'Enabled the RemoveMicrosoftCopilotApp Windows policy.'
}
function Set-BrowserUrlBlockList {
param([string]$RegistryPath)
New-Item -Path $RegistryPath -Force | Out-Null
# Remove only numeric URLBlocklist entries so this script owns a predictable list.
# A newly created registry key may cause Get-ItemProperty to emit no object in
# Windows PowerShell 5.1, so inspect its properties only when a result exists.
$registryValues = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue
if ($null -ne $registryValues) {
$registryValues.PSObject.Properties |
Where-Object Name -match '^\d+$' |
ForEach-Object {
Remove-ItemProperty -Path $RegistryPath -Name $_.Name -ErrorAction SilentlyContinue
}
}
for ($index = 0; $index -lt $blockedBrowserPatterns.Count; $index++) {
New-ItemProperty -Path $RegistryPath `
-Name ($index + 1).ToString() `
-PropertyType String `
-Value $blockedBrowserPatterns[$index] `
-Force | Out-Null
}
}
function Set-ManagedBrowserPolicies {
Set-BrowserUrlBlockList -RegistryPath 'HKLM:\SOFTWARE\Policies\Microsoft\Edge\URLBlocklist'
Set-BrowserUrlBlockList -RegistryPath 'HKLM:\SOFTWARE\Policies\Google\Chrome\URLBlocklist'
Write-Log 'Configured Microsoft Edge and Google Chrome URL block lists.'
}
function Set-FirefoxPolicy {
$firefoxRoots = @(
(Join-Path $env:ProgramFiles 'Mozilla Firefox'),
(Join-Path ${env:ProgramFiles(x86)} 'Mozilla Firefox')
) | Where-Object { $_ -and (Test-Path -LiteralPath $_) }
foreach ($firefoxRoot in $firefoxRoots) {
$distributionDirectory = Join-Path $firefoxRoot 'distribution'
$policyPath = Join-Path $distributionDirectory 'policies.json'
New-Item -ItemType Directory -Path $distributionDirectory -Force | Out-Null
$policyDocument = [ordered]@{ policies = [ordered]@{} }
if (Test-Path -LiteralPath $policyPath) {
try {
$existing = Get-Content -LiteralPath $policyPath -Raw | ConvertFrom-Json
if ($existing.policies) {
$policyDocument = $existing
}
}
catch {
$backupPath = "$policyPath.pre-copilot-block.bak"
Copy-Item -LiteralPath $policyPath -Destination $backupPath -Force
Write-Log "Existing Firefox policy was invalid JSON and was backed up to $backupPath"
}
}
$policyDocument.policies | Add-Member `
-NotePropertyName 'WebsiteFilter' `
-NotePropertyValue ([ordered]@{ Block = $blockedBrowserPatterns }) `
-Force
$policyDocument |
ConvertTo-Json -Depth 20 |
Set-Content -LiteralPath $policyPath -Encoding UTF8
Write-Log "Configured Mozilla Firefox URL blocking: $policyPath"
}
}
function Set-HostsFileBlock {
$hostsPath = Join-Path $env:SystemRoot 'System32\drivers\etc\hosts'
$beginMarker = '# BEGIN Microsoft Consumer Copilot block'
$endMarker = '# END Microsoft Consumer Copilot block'
$content = Get-Content -LiteralPath $hostsPath -Raw
$escapedBegin = [regex]::Escape($beginMarker)
$escapedEnd = [regex]::Escape($endMarker)
$content = [regex]::Replace(
$content,
"(?ms)\r?\n?$escapedBegin.*?$escapedEnd\r?\n?",
[Environment]::NewLine
)
$blockLines = @($beginMarker)
foreach ($hostName in $blockedHostNames) {
$blockLines += "0.0.0.0`t$hostName"
$blockLines += "::1`t$hostName"
}
$blockLines += $endMarker
$newContent = $content.TrimEnd() +
[Environment]::NewLine +
($blockLines -join [Environment]::NewLine) +
[Environment]::NewLine
Set-Content -LiteralPath $hostsPath -Value $newContent -Encoding ASCII
Clear-DnsClientCache
Write-Log 'Added consumer Copilot host names to the Windows hosts file.'
}
function Set-CopilotAppLockerRule {
if (-not (Get-Command Set-AppLockerPolicy -ErrorAction SilentlyContinue)) {
Install-CopilotRemovalWatchdog
Write-Log 'AppLocker is unavailable. Installed the removal watchdog fallback instead.'
return
}
$xml = @"
"@
$xmlPath = Join-Path $logDirectory 'Microsoft-Copilot-AppLocker.xml'
Set-Content -LiteralPath $xmlPath -Value $xml -Encoding UTF8
Set-AppLockerPolicy -XmlPolicy $xmlPath -Merge
& sc.exe config AppIDSvc start= auto | Out-Null
Start-Service -Name AppIDSvc -ErrorAction SilentlyContinue
Write-Log 'Merged the Microsoft Copilot packaged-app deny rule into the local AppLocker policy.'
}
function Install-CopilotRemovalWatchdog {
$watchdogPath = Join-Path $logDirectory 'Remove-Microsoft-Copilot-Watchdog.ps1'
$watchdogScript = @'
$installed = @(Get-AppxPackage -AllUsers -Name 'Microsoft.Copilot' -ErrorAction SilentlyContinue)
foreach ($package in $installed) {
Remove-AppxPackage -Package $package.PackageFullName -AllUsers -ErrorAction SilentlyContinue
}
$provisioned = @(
Get-AppxProvisionedPackage -Online |
Where-Object {
$_.DisplayName -eq 'Microsoft.Copilot' -or
$_.PackageName -like 'Microsoft.Copilot_*'
}
)
foreach ($package in $provisioned) {
Remove-AppxProvisionedPackage -Online -PackageName $package.PackageName -AllUsers -ErrorAction SilentlyContinue |
Out-Null
}
'@
Set-Content -LiteralPath $watchdogPath -Value $watchdogScript -Encoding UTF8
$action = New-ScheduledTaskAction `
-Execute 'powershell.exe' `
-Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$watchdogPath""
$startupTrigger = New-ScheduledTaskTrigger -AtStartup
$logonTrigger = New-ScheduledTaskTrigger -AtLogOn
$repeatingTrigger = New-ScheduledTaskTrigger `
-Once `
-At ((Get-Date).AddMinutes(2)) `
-RepetitionInterval (New-TimeSpan -Minutes 30)
$principal = New-ScheduledTaskPrincipal `
-UserId 'SYSTEM' `
-LogonType ServiceAccount `
-RunLevel Highest
Register-ScheduledTask `
-TaskName 'Block Microsoft Consumer Copilot' `
-Action $action `
-Trigger @($startupTrigger, $logonTrigger, $repeatingTrigger) `
-Principal $principal `
-Description 'Repeatedly removes the consumer Microsoft Copilot package when AppLocker is unavailable.' `
-Force | Out-Null
}
function Get-ComplianceState {
$findings = [System.Collections.Generic.List[string]]::new()
if (Get-AppxPackage -AllUsers -Name 'Microsoft.Copilot' -ErrorAction SilentlyContinue) {
$findings.Add('Microsoft.Copilot is installed.')
}
if (
Get-AppxProvisionedPackage -Online |
Where-Object {
$_.DisplayName -eq 'Microsoft.Copilot' -or
$_.PackageName -like 'Microsoft.Copilot_*'
}
) {
$findings.Add('Microsoft.Copilot is provisioned for new user profiles.')
}
$removePolicy = Get-ItemPropertyValue `
-Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI' `
-Name 'RemoveMicrosoftCopilotApp' `
-ErrorAction SilentlyContinue
if ($removePolicy -ne 1) {
$findings.Add('RemoveMicrosoftCopilotApp policy is not enabled.')
}
foreach ($browserPath in @(
'HKLM:\SOFTWARE\Policies\Microsoft\Edge\URLBlocklist',
'HKLM:\SOFTWARE\Policies\Google\Chrome\URLBlocklist'
)) {
$values = @(
(Get-ItemProperty -Path $browserPath -ErrorAction SilentlyContinue).PSObject.Properties |
Where-Object Name -match '^\d+$' |
Select-Object -ExpandProperty Value
)
if ($values -notcontains '*://copilot.microsoft.com/*') {
$findings.Add("Consumer Copilot URL isn't blocked at $browserPath")
}
}
if (Get-Command Get-AppLockerPolicy -ErrorAction SilentlyContinue) {
$localAppLocker = Get-AppLockerPolicy -Local -Xml -ErrorAction SilentlyContinue
if ($localAppLocker -notmatch [regex]::Escape($copilotProduct)) {
$findings.Add('The local AppLocker policy does not contain the Copilot deny rule.')
}
}
elseif (-not (Get-ScheduledTask -TaskName 'Block Microsoft Consumer Copilot' -ErrorAction SilentlyContinue)) {
$findings.Add('Neither AppLocker nor the Microsoft Copilot removal watchdog is configured.')
}
return $findings
}
if (-not (Test-IsAdministrator)) {
throw 'Run this script from an elevated Windows PowerShell session (Run as administrator).'
}
Write-Log "Starting mode: $Mode"
if ($Mode -eq 'Enforce') {
Remove-CopilotPackages
Set-CopilotRemovalPolicy
Set-ManagedBrowserPolicies
Set-FirefoxPolicy
Set-HostsFileBlock
Set-CopilotAppLockerRule
}
$remainingFindings = @(Get-ComplianceState)
if ($remainingFindings.Count -eq 0) {
Write-Log 'COMPLIANT: consumer Microsoft Copilot is removed and the local controls are present.'
exit 0
}
foreach ($finding in $remainingFindings) {
Write-Log "NONCOMPLIANT: $finding"
}
exit 1
