~/2025/11/26/powershell-windows-server-disable-smbv1-fleet-wide-and-verify-it-stuck.md
PowerShell: Windows Server – Disable SMBv1 Fleet-Wide and Verify
--- author: Tom Lasswell date: read: 4 min in: [ps, scripts] tags: [powershell, windows] ---
$ grep -n '^#' post.md
SMBv1 isn't installed by default on Windows Server 2019 and later, but "not installed by default" and "not installed anywhere" are two different statements once you have a server fleet with years of in-place upgrades behind it. Microsoft's position is blunt: SMBv1 "has significant security vulnerabilities, and we strongly encourage you not to use it," and if something still needs it, update that thing rather than reinstall SMBv1. Disabling it is one of the highest-value changes you can make, as long as you first find out who's still using it. This script does all three steps against a list of servers: it turns on SMBv1 access auditing and counts the audit events, it disables the SMBv1 server and the SMB1Protocol optional feature, and because disabling the feature only finishes at restart, it reports which servers are clean, which are pending a restart, and which failed.
Requirements
- Windows PowerShell 5.1 or later on the admin workstation, with PowerShell remoting (WinRM) reachable on every target.
- Windows Server 2012 R2 or later on the targets. That's where the
SMB1Protocoloptional feature and theSmbSharecmdlets (Get-SmbServerConfiguration,Set-SmbServerConfiguration) exist. Server 2008 R2 uses a registry value instead (see Notes). - Local administrator rights on each target.
- Time. Run the
-Auditpass and leave it for long enough to cover month-end jobs, backups and scanners before you run the disable pass.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
-ComputerName | String | No | Servers to process. If omitted, -ComputerListPath is used. |
-ComputerListPath | String | No | Text file with one server name per line. |
-Audit | Switch | No | Turns on AuditSmb1Access and reports current state and event 3000 counts. Changes nothing else. |
-Verify | Switch | No | Reports current state and event 3000 counts only. Changes nothing. |
-AuditDays | Int | No | How many days of Microsoft-Windows-SMBServer/Audit events to count. Default 14. |
-OutputPath | String | No | CSV of per-server results. Default .\smbv1-status.csv. |
-ThrottleLimit | Int | No | Maximum concurrent remote connections. Default 16. |
With neither -Audit nor -Verify, the script disables SMBv1.
Usage
Turn on auditing across the fleet and see where SMBv1 is still installed:
.\Disable-Smb1Fleetwide.ps1 -ComputerListPath C:\Lists\servers.txt -Audit
A couple of weeks later, check who has been connecting with SMBv1 (event ID 3000):
.\Disable-Smb1Fleetwide.ps1 -ComputerListPath C:\Lists\servers.txt -Verify -AuditDays 30
Look at the actual clients on a server that logged events before you break them:
Get-WinEvent -ComputerName SRV-FILE01 -FilterHashtable @{ LogName = "Microsoft-Windows-SMBServer/Audit"; Id = 3000 } -MaxEvents 20 |
Format-List -Property TimeCreated, Message
Disable SMBv1 on the servers that came back clean, then see which need a restart:
.\Disable-Smb1Fleetwide.ps1 -ComputerListPath C:\Lists\servers-clean.txt
Re-run in verify mode after the maintenance window's restarts to confirm it stuck:
.\Disable-Smb1Fleetwide.ps1 -ComputerListPath C:\Lists\servers-clean.txt -Verify -OutputPath C:\Reports\smbv1-post-restart.csv
Sample console output from a disable run:
Processing 3 server(s), throttle limit 16, mode Disable ...
SRV-FILE01: disabled, restart pending (feature DisablePending, server SMB1 off)
SRV-FILE02: already disabled, nothing to do
WARNING: Unreachable: SRV-PRINT03 - WinRM cannot complete the operation. Verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled and allows access from this computer.
Results written to .\smbv1-status.csv
Script
<#
.SYNOPSIS
Audits, disables and verifies SMBv1 across a list of Windows servers.
.DESCRIPTION
For each target server, reads the SMB1Protocol optional feature state, the
SMB server's EnableSMB1Protocol and AuditSmb1Access settings, and the number
of event ID 3000 entries (SMBv1 client access) in the
Microsoft-Windows-SMBServer/Audit log. -Audit also turns auditing on.
Without -Audit or -Verify, the script turns off the SMBv1 server
(Set-SmbServerConfiguration, takes effect without a restart), disables the
SMB1Protocol feature with -NoRestart, re-reads the feature state and records
whether a restart is pending. Results go to the console and a CSV.
.PARAMETER ComputerName
One or more servers to process.
.PARAMETER ComputerListPath
Text file with one server name per line, used when -ComputerName is not supplied.
.PARAMETER Audit
Turns on AuditSmb1Access and reports state. Makes no other change.
.PARAMETER Verify
Reports state only. Makes no changes.
.PARAMETER AuditDays
Days of SMBServer/Audit events to count. Default 14.
.PARAMETER OutputPath
CSV of per-server results. Default .\smbv1-status.csv.
.PARAMETER ThrottleLimit
Maximum concurrent remote connections. Default 16.
.EXAMPLE
.\Disable-Smb1Fleetwide.ps1 -ComputerListPath C:\Lists\servers.txt -Audit
.EXAMPLE
.\Disable-Smb1Fleetwide.ps1 -ComputerListPath C:\Lists\servers.txt
.NOTES
Author : Thomas Lasswell (https://www.techcolumnist.com)
Version : 1.0 (2025-11-26)
Requires: PowerShell remoting on targets; Windows Server 2012 R2 or later; local admin
.LINK
https://learn.microsoft.com/en-us/windows-server/storage/file-server/troubleshoot/detect-enable-and-disable-smbv1-v2-v3
#>
[CmdletBinding()]
param (
[string[]]$ComputerName,
[string]$ComputerListPath,
[switch]$Audit,
[switch]$Verify,
[ValidateRange(1, 365)]
[int]$AuditDays = 14,
[string]$OutputPath = ".\smbv1-status.csv",
[int]$ThrottleLimit = 16
)
if ($Audit -and $Verify) {
throw "Use -Audit or -Verify, not both."
}
if (-not $ComputerName) {
if (-not $ComputerListPath -or -not (Test-Path -Path $ComputerListPath)) {
throw "Supply -ComputerName or a valid -ComputerListPath."
}
$ComputerName = Get-Content -Path $ComputerListPath | Where-Object { $_.Trim() -ne "" }
}
$mode = if ($Audit) { "Audit" } elseif ($Verify) { "Verify" } else { "Disable" }
Write-Host "Processing $(@($ComputerName).Count) server(s), throttle limit $ThrottleLimit, mode $mode ..."
# Runs on each remote server. Mode and AuditDays arrive through -ArgumentList.
$smb1Script = {
param ($Mode, $AuditDays)
$result = [ordered]@{
StartingFeature = $null
EndingFeature = $null
ServerSmb1Enabled = $null
AuditEnabled = $null
Smb1AccessEvents = $null
Action = "none"
RestartNeeded = $false
Error = $null
}
try {
$feature = Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -ErrorAction Stop
$result.StartingFeature = [string]$feature.State
if ($Mode -eq "Audit") {
Set-SmbServerConfiguration -AuditSmb1Access $true -Force -ErrorAction Stop
$result.Action = "audit enabled"
}
if ($Mode -eq "Disable") {
# Server side first: takes effect immediately, no restart needed.
if ((Get-SmbServerConfiguration).EnableSMB1Protocol) {
Set-SmbServerConfiguration -EnableSMB1Protocol $false -Force -ErrorAction Stop
$result.Action = "server SMB1 off"
}
# Then disable the feature (SMBv1 client and server). Finishes at restart.
if ($feature.State -ne "Disabled") {
$image = Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol -NoRestart -ErrorAction Stop
$result.RestartNeeded = [bool]$image.RestartNeeded
$result.Action = "disabled"
}
}
$config = Get-SmbServerConfiguration
$result.ServerSmb1Enabled = $config.EnableSMB1Protocol
$result.AuditEnabled = $config.AuditSmb1Access
$result.EndingFeature = [string](Get-WindowsOptionalFeature -Online -FeatureName SMB1Protocol).State
$filter = @{
LogName = "Microsoft-Windows-SMBServer/Audit"
Id = 3000
StartTime = (Get-Date).AddDays(-$AuditDays)
}
$result.Smb1AccessEvents = @(Get-WinEvent -FilterHashtable $filter -ErrorAction SilentlyContinue).Count
} catch {
$result.Action = "failed"
$result.Error = $_.Exception.Message
}
[PSCustomObject]$result
}
$invokeParams = @{
ComputerName = $ComputerName
ScriptBlock = $smb1Script
ArgumentList = $mode, $AuditDays
ThrottleLimit = $ThrottleLimit
ErrorAction = "SilentlyContinue"
ErrorVariable = "remoteErrors"
}
$results = Invoke-Command @invokeParams
foreach ($result in ($results | Sort-Object -Property PSComputerName)) {
$target = $result.PSComputerName
if ($result.Error) {
Write-Host "$target`: failed - $($result.Error)"
} elseif ($result.Action -eq "disabled") {
$state = if ($result.RestartNeeded) { "restart pending" } else { "no restart needed" }
Write-Host "$target`: disabled, $state (feature $($result.EndingFeature), server SMB1 $(if ($result.ServerSmb1Enabled) { 'on' } else { 'off' }))"
} elseif ($result.EndingFeature -eq "Disabled" -and -not $result.ServerSmb1Enabled) {
Write-Host "$target`: already disabled, nothing to do"
} else {
Write-Host "$target`: SMBv1 feature $($result.EndingFeature), server SMB1 enabled $($result.ServerSmb1Enabled), audit $($result.AuditEnabled), $($result.Smb1AccessEvents) access event(s) in $AuditDays days"
}
}
$results |
Select-Object -Property PSComputerName, StartingFeature, EndingFeature, ServerSmb1Enabled, AuditEnabled, Smb1AccessEvents, Action, RestartNeeded, Error |
Export-Csv -Path $OutputPath -NoTypeInformation
foreach ($failure in $remoteErrors) {
Write-Warning "Unreachable: $($failure.TargetObject) - $($failure.Exception.Message)"
}
Write-Host "Results written to $OutputPath"
Notes
- Two switches, two effects.
Set-SmbServerConfiguration -EnableSMB1Protocol $falsestops the SMB server from accepting SMBv1, and Microsoft notes you "don't have to restart the computer" after it.Disable-WindowsOptionalFeature -FeatureName SMB1Protocoldisables the SMBv1 feature, client and server, and needs a restart to finish, which is why the script recordsRestartNeededand the post-change feature state separately. Without-Removeit leaves the payload files on disk, so the feature can be re-enabled without installation media; add-Removeonly if you want the binaries gone too. Don't treat a server as done until a-Verifypass after the restart showsEndingFeatureasDisabled. - Auditing only sees the server side. With
AuditSmb1Accesson, every SMBv1 connection attempt to that server logs event ID 3000 inMicrosoft-Windows-SMBServer/Audit; open the events (theGet-WinEventexample above) to see the client details. It tells you nothing about servers that initiate SMBv1 connections outward, for example to an old NAS or a scan-to-folder copier. Check those devices from the other side or with the vendor. - Domain controllers. Microsoft warns that "some systems require access to the SYSVOL folder or other file shares but don't support SMBv2 or SMBv3," such as legacy Windows and older Linux or partner systems. Audit DCs longest and do them last. Microsoft keeps an SMB1 Product Clearinghouse (https://aka.ms/stillneedssmb1) of products that required SMBv1 and the updates that remove the requirement.
- Group Policy instead of a script. For ongoing enforcement Microsoft documents registry preference items:
HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\SMB1=0(REG_DWORD) for the server, and for the client,HKLM\SYSTEM\CurrentControlSet\services\mrxsmb10\Start=4plusLanmanWorkstation\DependOnServicereplaced withBowser,MRxSmb20,NSI. Both need a restart. That's also the method for Server 2008 R2 and Windows 7, which have noSMB1Protocolfeature. - Don't confuse this with signing. On Windows 11 24H2 and Windows Server 2025, SMB signing is required by default, which can break connections to third-party SMB servers that don't sign. Microsoft is explicit that changing which SMB versions are enabled doesn't change the signing requirement, so don't re-enable SMBv1 (or disable signing) to "fix" those failures.
- I stage this in batches by server role rather than all at once: file and print servers first (where legacy SMBv1 clients are most likely to show up in the audit log), then application servers, and domain controllers last, with a week between batches to let any breakage show up in help desk tickets before the blast radius grows.