<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Charon&apos;s Blog</title><description>Some problems and some solutions</description><link>https://blog.drcharon.com/</link><language>en</language><item><title>Integrating a Brother Scanner with SharePoint Using IIS FTP and Power Automate</title><link>https://blog.drcharon.com/posts/printer-ftp-sharepoint/en/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/printer-ftp-sharepoint/en/</guid><description>Learn how to integrate a Brother multifunction printer that does not support Scan to SharePoint by using IIS FTP, an On-premises Data Gateway, and Power Automate to automatically upload scanned documents to SharePoint Online.</description><pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;As our organization gradually migrated its document storage to SharePoint Online, we encountered a new challenge.&lt;/p&gt;
&lt;p&gt;Traditional multifunction printers (MFPs) typically support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scan to Email&lt;/li&gt;
&lt;li&gt;Scan to SMB&lt;/li&gt;
&lt;li&gt;Scan to FTP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, most Brother printers—especially entry-level and mid-range models—do not support scanning directly to SharePoint Online.&lt;/p&gt;
&lt;p&gt;Continuing to use a traditional SMB shared folder would mean scanned documents remain outside the SharePoint ecosystem, losing the benefits of centralized permission management, version control, and collaboration.&lt;/p&gt;
&lt;p&gt;Our objective was straightforward:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Keep the scanning experience unchanged for end users while ensuring every scanned document ultimately ends up in SharePoint.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;Solution Architecture&lt;/h1&gt;
&lt;p&gt;After evaluating several options, we adopted the following architecture:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Brother Scanner
        │
Scan to FTP
        │
Windows Server IIS FTP
        │
Power Automate
        │
SharePoint Document Library
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The entire process is completely transparent to end users.&lt;/p&gt;
&lt;p&gt;Users simply select &lt;strong&gt;Scan to FTP&lt;/strong&gt; on the Brother printer.&lt;/p&gt;
&lt;p&gt;The system automatically performs the remaining tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Save the scanned document to the FTP server.&lt;/li&gt;
&lt;li&gt;Detect the newly created file.&lt;/li&gt;
&lt;li&gt;Upload it to the designated SharePoint document library.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No manual intervention is required.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Environment&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Server&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows Server 2016 Standard&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Printer&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Brother DCP-L2640DW&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Automation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft Power Automate&lt;/li&gt;
&lt;li&gt;On-premises Data Gateway&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Destination&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SharePoint Online&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;FTP Server&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IIS FTP Server&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Installing IIS FTP&lt;/h1&gt;
&lt;p&gt;Windows Server does not install the FTP service by default.&lt;/p&gt;
&lt;p&gt;Open &lt;strong&gt;Server Manager&lt;/strong&gt; and launch the &lt;strong&gt;Add Roles and Features Wizard&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server Manager
    ↓
Add Roles and Features
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Select:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Web Server (IIS)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Under &lt;strong&gt;Role Services&lt;/strong&gt;, install the FTP components in addition to the default IIS features:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Web Server (IIS)
├── Common HTTP Features
├── Health and Diagnostics
├── Performance
├── Security
├── ...
└── FTP Server
    ├── FTP Service
    └── FTP Extensibility
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These two components serve different purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FTP Service&lt;/strong&gt; provides the core FTP functionality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FTP Extensibility&lt;/strong&gt; enables advanced authentication methods and future extensibility, so it is recommended to install it together with the FTP Service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After the installation completes, a new &lt;strong&gt;FTP Sites&lt;/strong&gt; node will appear in IIS Manager.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Creating an FTP Site&lt;/h1&gt;
&lt;p&gt;In this solution, the FTP server is used only as a temporary staging area for scanned documents.&lt;/p&gt;
&lt;p&gt;Create a dedicated directory on the server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then open IIS Manager:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server Manager
    ↓
Tools
    ↓
Internet Information Services (IIS) Manager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expand the server node:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PRINTER
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Right-click &lt;strong&gt;Sites&lt;/strong&gt; and select:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Add FTP Site...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure the site using the following settings:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Site Name
Scanner FTP

Physical Path
C:\FTPScan

IP Address
192.168.x.x

Port
21

SSL
No SSL
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;strong&gt;Why not use FTPS?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Brother DCP-L2640DW supports standard FTP out of the box, while FTPS (FTP over SSL) compatibility varies depending on the printer model and firmware version.&lt;/p&gt;
&lt;p&gt;Since our initial goal was to validate the complete workflow, we chose standard FTP for the first deployment. Because both the FTP server and the printer reside within the internal corporate network, this approach provides a reasonable balance between simplicity and functionality.&lt;/p&gt;
&lt;p&gt;If security or compliance requirements change in the future, upgrading to FTPS can be considered without changing the overall architecture.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For authentication, select:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Basic Authentication
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For authorization:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Specified users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grant access only to the dedicated FTP account used by the scanner.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Creating a Dedicated FTP Account&lt;/h1&gt;
&lt;p&gt;Instead of using an administrator account, create a dedicated local user with the minimum permissions required.&lt;/p&gt;
&lt;p&gt;Open:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Computer Management
    ↓
Local Users and Groups
    ↓
Users
    ↓
New User...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create the following account:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Username
scanftp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using a dedicated account follows the principle of least privilege and simplifies future auditing and maintenance.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Planning the Directory Structure&lt;/h1&gt;
&lt;p&gt;Although this deployment initially included only a single printer, it is worth planning a scalable directory structure from the beginning.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan
├── MTL-P73
├── MTL-P74
├── QC-P01
└── HR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each Brother printer is configured to use its own &lt;strong&gt;Store Directory&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Store Directory

MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows Power Automate to monitor different folders and automatically upload documents to different SharePoint document libraries without requiring any changes to the printer configuration.&lt;/p&gt;
&lt;p&gt;It also makes the solution easy to expand as additional printers are deployed.&lt;/p&gt;
&lt;h1&gt;Troubleshooting #1: Windows Defender Firewall&lt;/h1&gt;
&lt;p&gt;Local FTP testing worked perfectly.&lt;/p&gt;
&lt;p&gt;However, client computers were unable to connect to the FTP server.&lt;/p&gt;
&lt;h2&gt;Verify the FTP Service, FTP Site, and Port 21&lt;/h2&gt;
&lt;p&gt;Run the following commands on the FTP server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Windows\system32&amp;gt; systeminfo | findstr /B /C:&quot;OS Name&quot; /C:&quot;OS Version&quot;

OS Name:    Microsoft Windows Server 2016 Standard
OS Version: 10.0.14393 N/A Build 14393

PS C:\Windows\system32&amp;gt; Get-Service ftpsvc

Status     Name     DisplayName
------     ----     -----------
Running    ftpsvc   Microsoft FTP Service

PS C:\Windows\system32&amp;gt; Get-WebSite

Name             ID     State     Physical Path                 Bindings
----             --     -----     -------------                 --------
Default Web Site 1      Started   %SystemDrive%\inetpub\wwwroot http *:80:
Scanner FTP      2      Started   C:\FTPScan                    ftp 192.168.1.129:21:

PS C:\Windows\system32&amp;gt; netstat -ano | findstr :21

TCP    0.0.0.0:21                  0.0.0.0:0              LISTENING
TCP    [::]:21                     [::]:0                 LISTENING
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Analysis&lt;/h2&gt;
&lt;p&gt;✅ The FTP service is running.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Running    ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ The FTP Site has started successfully.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scanner FTP      Started
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ Port 21 is listening.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0.0.0.0:21    LISTENING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, there were no obvious issues on the server side.&lt;/p&gt;
&lt;h3&gt;Test the TCP Connection&lt;/h3&gt;
&lt;p&gt;Open PowerShell on a client computer and run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test-NetConnection 192.168.1.129 -Port 21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WARNING: TCP connect to (192.168.1.129 : 21) failed

ComputerName     : 192.168.1.129
RemoteAddress    : 192.168.1.129
RemotePort       : 21
SourceAddress    : 192.168.1.191
PingSucceeded    : True
TcpTestSucceeded : False
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Check the Firewall Rules&lt;/h2&gt;
&lt;p&gt;Run the following command on the FTP server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-NetFirewallRule -DisplayGroup &quot;FTP Server&quot; | Select DisplayName, Enabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DisplayName                                 Enabled
-----------                                 -------
FTP Server (FTP Traffic-In)                 True
FTP Server (FTP Traffic-Out)                True
FTP Server Secure (FTP SSL Traffic-In)      True
FTP Server Secure (FTP SSL Traffic-Out)     True
FTP Server Passive (FTP Passive Traffic-In) True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything appeared to be configured correctly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
Since this server is joined to an Active Directory domain, we suspected that the &lt;strong&gt;Domain Firewall Profile&lt;/strong&gt; might be blocking FTP traffic.&lt;/p&gt;
&lt;p&gt;To verify this assumption, we temporarily disabled the Domain Firewall Profile.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-NetFirewallProfile -Profile Domain -Enabled False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722115720.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After disabling the Domain Firewall Profile, the result changed to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TcpTestSucceeded : True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Windows Defender Firewall (or a firewall policy applied through Group Policy) was blocking the FTP connection.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Do Not Leave the Firewall Disabled&lt;/h2&gt;
&lt;p&gt;Re-enable the firewall:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-NetFirewallProfile -Profile Domain -Enabled True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of relying on the firewall rules created automatically by IIS, we created an explicit inbound rule for FTP.&lt;/p&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;New-NetFirewallRule `
    -DisplayName &quot;Scanner FTP 21&quot; `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 21 `
    -Action Allow `
    -Profile Domain
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After adding this firewall rule, FTP connections worked successfully.&lt;/p&gt;
&lt;h1&gt;Troubleshooting #2: FTP Passive Mode&lt;/h1&gt;
&lt;p&gt;After resolving the firewall issue, the client was finally able to establish an FTP connection.&lt;/p&gt;
&lt;p&gt;However, a new problem appeared.&lt;/p&gt;
&lt;p&gt;The FTP session could be established successfully, but file uploads still failed.&lt;/p&gt;
&lt;p&gt;At this point, we knew that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Port 21 was reachable.&lt;/li&gt;
&lt;li&gt;The FTP service was running normally.&lt;/li&gt;
&lt;li&gt;Authentication succeeded.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The problem occurred &lt;strong&gt;after&lt;/strong&gt; the control connection had been established.
&lt;img src=&quot;Pasted%20image%2020260722120132.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Understanding FTP Passive Mode&lt;/h2&gt;
&lt;p&gt;FTP uses two separate connections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Control Connection&lt;/strong&gt; (TCP 21)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Connection&lt;/strong&gt; (a dynamically assigned TCP port)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When using &lt;strong&gt;Passive Mode&lt;/strong&gt;, the server opens a random TCP port for the data connection.&lt;/p&gt;
&lt;p&gt;If these dynamic ports are not allowed through the firewall, the FTP client can log in successfully but will fail when transferring files.&lt;/p&gt;
&lt;p&gt;This is a common issue when deploying an FTP server on Windows Server.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Configure a Passive Port Range&lt;/h2&gt;
&lt;p&gt;Open &lt;strong&gt;IIS Manager&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Select the server node, then open:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FTP Firewall Support
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure a dedicated passive port range.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Data Channel Port Range

50000-50050
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using a fixed range makes firewall configuration much simpler than allowing all dynamic ports.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Allow the Passive Ports Through Windows Firewall&lt;/h2&gt;
&lt;p&gt;After configuring the passive port range in IIS, Windows Defender Firewall must also allow those ports.&lt;/p&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;New-NetFirewallRule `
    -DisplayName &quot;FTP Passive Ports&quot; `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 50000-50050 `
    -Action Allow `
    -Profile Domain
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart FTP service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Restart-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;Printer Configuration&lt;/h1&gt;
&lt;p&gt;Configuring the Brother printer is very straightforward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FTP Server:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;192.168.x.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Port:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Passive Mode:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Enable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Store Directory:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the &lt;strong&gt;Username&lt;/strong&gt; and &lt;strong&gt;Password&lt;/strong&gt;, enter the local account that was created earlier.&lt;/p&gt;
&lt;p&gt;One thing to note is:&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Store Directory&lt;/strong&gt; is a path relative to the FTP Root.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Root

C:\FTPScan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Store Directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actual file storage location will be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan\MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;Power Automate&lt;/h1&gt;
&lt;p&gt;Since the files are stored on a local server, we used:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;On-premises Data Gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to connect to the local file system.&lt;/p&gt;
&lt;p&gt;The Flow itself is very simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When a file is created
        │
Get file content
        │
Create file (SharePoint)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No scripts are required.&lt;/p&gt;
&lt;p&gt;The entire workflow uses only Microsoft official connectors.&lt;/p&gt;
&lt;h2&gt;Step 1: Verify that the Gateway Is Installed&lt;/h2&gt;
&lt;p&gt;Do not create the Flow yet.&lt;/p&gt;
&lt;p&gt;On the server (&lt;strong&gt;PRINTER&lt;/strong&gt;), first verify whether the following is already installed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;On-premises data gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Search the Start menu for:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On-premises data gateway&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;then it is already installed.&lt;/p&gt;
&lt;p&gt;If not, install it first.&lt;/p&gt;
&lt;p&gt;Download it from:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://aka.ms/onpremgateway&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;During the installation, configure the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mode:&lt;/strong&gt; Standard&lt;/li&gt;
&lt;li&gt;Sign in with your &lt;code&gt;username@contasco.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gateway Name:&lt;/strong&gt; For example, &lt;code&gt;PRINTER-GW&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recovery Key:&lt;/strong&gt; Create a strong password and store it securely. It will be required if the Gateway needs to be migrated or restored in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 2: Create an Automated Cloud Flow&lt;/h2&gt;
&lt;p&gt;Open:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://make.powerautomate.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then navigate to:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create&lt;/strong&gt; → &lt;strong&gt;Automated cloud flow&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Enter the following:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flow Name&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FTP Scan to SharePoint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Choose your flow&apos;s trigger&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Search for:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;File System
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Select:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When a file is created (properties only)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722120836.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Step 3: Create the Connection&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722120934.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connection name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PRINTER File System&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Root folder&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;C:\FTPScan&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Windows&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Username&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The local account created earlier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Password&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The password for the Windows account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gateway&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Select the &lt;code&gt;PRINTER-GW&lt;/code&gt; gateway created earlier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Step 4: Configure the Trigger&lt;/h2&gt;
&lt;p&gt;Trigger:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When a file is created (properties only)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Folder&lt;/h3&gt;
&lt;p&gt;Click the &lt;strong&gt;folder icon&lt;/strong&gt; on the right (if available), or manually enter the path relative to the Root Folder:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the Root Folder is already:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the Trigger is actually monitoring:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan\MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722121152.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Step 5: Add the File Content Action&lt;/h2&gt;
&lt;p&gt;Click the &lt;strong&gt;➕&lt;/strong&gt; button in the middle.&lt;/p&gt;
&lt;p&gt;Select:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Add an action&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Search for:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;File System
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Select:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Get file content&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For &lt;strong&gt;Get file content → File&lt;/strong&gt;, select:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;body/Id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722121314.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After that, click the &lt;strong&gt;➕&lt;/strong&gt; button below to add the next action:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SharePoint — Create file&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722121351.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Configure the action:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722121532.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For &lt;strong&gt;File Name&lt;/strong&gt; and &lt;strong&gt;File Content&lt;/strong&gt;, select the dynamic content returned by &lt;strong&gt;Get file content&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Click &lt;strong&gt;Save&lt;/strong&gt; in the upper-right corner to save the Flow.&lt;/p&gt;
&lt;p&gt;The final Power Automate workflow is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When a file is created (properties only)
             │
             ▼
Get file content
             │
             ▼
SharePoint Create file
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;Final Result&lt;/h1&gt;
&lt;p&gt;The final workflow becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scan
    │
    ▼
FTP
    │
    ▼
Power Automate
    │
    ▼
SharePoint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For end users, nothing changes in the scanning process.&lt;/p&gt;
&lt;p&gt;However, every scanned document is ultimately stored in SharePoint, where it benefits from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Permission management&lt;/li&gt;
&lt;li&gt;Online collaboration&lt;/li&gt;
&lt;li&gt;Version control&lt;/li&gt;
&lt;li&gt;Centralized Microsoft 365 management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Throughout the entire deployment, the printer does not need to support SharePoint, nor is any third-party software required.&lt;/p&gt;
&lt;p&gt;This approach is also easy to replicate for other printers.&lt;/p&gt;
&lt;h1&gt;Toolbox&lt;/h1&gt;
&lt;p&gt;The following PowerShell and Windows commands were used throughout this deployment and can serve as a reference for future deployments and troubleshooting.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Check the FTP Service Status&lt;/h2&gt;
&lt;p&gt;Verify that the IIS FTP service is running.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the FTP service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Start-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Stop the FTP service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stop-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart the FTP service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Restart-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Check Whether Port 21 Is Listening&lt;/h2&gt;
&lt;p&gt;Verify that the FTP server is listening on TCP port 21.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netstat -ano | findstr :21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LISTENING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then IIS has successfully bound to port 21.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Test Network Connectivity&lt;/h2&gt;
&lt;p&gt;Verify that the client can reach the FTP server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test-NetConnection 192.168.x.x -Port 21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pay attention to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TcpTestSucceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;True&lt;/strong&gt; — Network connectivity is working.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;False&lt;/strong&gt; — Continue troubleshooting the firewall or network configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Test FTP Login&lt;/h2&gt;
&lt;p&gt;Use the built-in Windows FTP client to test the connection.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ftp 192.168.x.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After logging in successfully, use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dir
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to list the directory contents.&lt;/p&gt;
&lt;p&gt;Exit the session with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bye
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Check the Windows Firewall Status&lt;/h2&gt;
&lt;p&gt;View the status of all Firewall Profiles.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-NetFirewallProfile
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Temporarily Disable the Firewall (Troubleshooting Only)&lt;/h2&gt;
&lt;p&gt;Use this only to verify whether the firewall is causing the issue.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-NetFirewallProfile -Profile Domain -Enabled False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Re-enable it afterward:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-NetFirewallProfile -Profile Domain -Enabled True
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;It is not recommended to leave the firewall disabled. This should only be used for troubleshooting.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Create an FTP Firewall Rule&lt;/h2&gt;
&lt;p&gt;Allow the FTP control connection (TCP 21).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;New-NetFirewallRule `
    -DisplayName &quot;Scanner FTP&quot; `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 21 `
    -Action Allow `
    -Profile Domain
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. View Firewall Rules&lt;/h2&gt;
&lt;p&gt;List all FTP-related firewall rules.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-NetFirewallRule |
Where-Object DisplayName -like &quot;*FTP*&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Recommended Troubleshooting Order&lt;/h2&gt;
&lt;p&gt;It is recommended to troubleshoot in the following order instead of changing multiple settings at the same time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Is the FTP service running?&lt;/li&gt;
&lt;li&gt;Is port 21 listening?&lt;/li&gt;
&lt;li&gt;Can you log in to the FTP server locally?&lt;/li&gt;
&lt;li&gt;Does &lt;code&gt;Test-NetConnection&lt;/code&gt; succeed?&lt;/li&gt;
&lt;li&gt;Is the firewall blocking the connection?&lt;/li&gt;
&lt;li&gt;Is Passive Mode configured correctly?&lt;/li&gt;
&lt;li&gt;Does the Brother FTP Test pass?&lt;/li&gt;
&lt;li&gt;Is the file successfully written to the FTP Root?&lt;/li&gt;
&lt;li&gt;Is Power Automate triggered?&lt;/li&gt;
&lt;li&gt;Does the file successfully appear in SharePoint?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Following this sequence makes it much easier to isolate the root cause and avoids making unnecessary configuration changes.&lt;/p&gt;
</content:encoded></item><item><title>Brother 扫描仪无缝接入 SharePoint —— IIS FTP + Power Automate 自动上传方案</title><link>https://blog.drcharon.com/posts/printer-ftp-sharepoint/zh/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/printer-ftp-sharepoint/zh/</guid><description>记录如何让不支持 Scan to SharePoint 的 Brother 多功能打印机，通过 IIS FTP、On-premises Data Gateway 和 Power Automate，实现扫描文件自动上传到 SharePoint。</description><pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;随着公司文件逐步迁移到 SharePoint，我们遇到了一个新的问题。&lt;/p&gt;
&lt;p&gt;传统的多功能打印机（MFP）通常支持：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scan to Email&lt;/li&gt;
&lt;li&gt;Scan to SMB&lt;/li&gt;
&lt;li&gt;Scan to FTP&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是大多数型号，尤其是中低端 Brother 设备，并不支持直接扫描到 Scan to email/SharePoint Online。&lt;/p&gt;
&lt;p&gt;如果继续使用传统 SMB 共享目录，那么扫描件就会脱离 SharePoint 的权限管理、版本控制以及协作体系。&lt;/p&gt;
&lt;p&gt;我们的目标很明确：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;保持用户使用习惯不变，同时让所有扫描文件最终进入 SharePoint。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;最终架构&lt;/h1&gt;
&lt;p&gt;最终采用了下面的架构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Brother Scanner
        │
Scan to FTP
        │
Windows Server IIS FTP
        │
Power Automate
        │
SharePoint Document Library
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;整个过程对于用户完全透明。&lt;/p&gt;
&lt;p&gt;用户只需要在打印机上按下 &lt;strong&gt;Scan to FTP&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;系统会自动完成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;扫描到 FTP&lt;/li&gt;
&lt;li&gt;检测新文件&lt;/li&gt;
&lt;li&gt;上传至 SharePoint&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;无需任何人工参与。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;环境&lt;/h1&gt;
&lt;p&gt;服务器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows Server 2016 Standard&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;打印机：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Brother DCP-L2640DW&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文件上传：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft Power Automate&lt;/li&gt;
&lt;li&gt;On-premises Data Gateway&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目标平台：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SharePoint Online&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;FTP：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IIS FTP Server&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;安装 IIS FTP&lt;/h1&gt;
&lt;p&gt;Windows Server 默认并不会安装 FTP 服务。&lt;/p&gt;
&lt;p&gt;需要通过 &lt;strong&gt;Server Manager&lt;/strong&gt; 添加 IIS（Internet Information Services）相关角色。&lt;/p&gt;
&lt;p&gt;打开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server Manager
→ Add Roles and Features
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;选择：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Web Server (IIS)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;strong&gt;Role Services&lt;/strong&gt; 中，除了默认的 Web Server 组件外，还需要额外安装 FTP 相关服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Web Server (IIS)
├── Common HTTP Features
├── Health and Diagnostics
├── Performance
├── Security
├── ...
└── FTP Server
    ├── FTP Service
    └── FTP Extensibility
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FTP Service&lt;/strong&gt;：提供 FTP 服务本身。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FTP Extensibility&lt;/strong&gt;：支持扩展身份验证和后续高级功能，建议一并安装。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;安装完成后，在 IIS Manager 中即可看到 &lt;strong&gt;FTP Sites&lt;/strong&gt; 节点。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;创建 FTP Site&lt;/h2&gt;
&lt;p&gt;本项目中，FTP 仅作为扫描文件的临时缓冲区（Staging Area）。&lt;/p&gt;
&lt;p&gt;因此，我们在服务器上创建了专用目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后在 IIS 中新建 FTP Site：
打开IIS manager&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server Manager
    ↓
Tools
    ↓
Internet Information Services (IIS) Manager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PRINTER
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;创建新的 FTP Site&lt;/h2&gt;
&lt;p&gt;右键：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Sites
    ↓
Add FTP Site...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考配置如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Site Name
Scanner FTP

Physical Path
C:\FTPScan

IP Address
192.168.x.x

Port
21

No SSL
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE] 为什么不用 SSL？
Brother DCP-L2640DW 支持普通 FTP，而 FTPS（FTP over SSL）兼容性因型号和固件而异。我们的目标是先验证整个流程，等以后如果有合规要求，再升级到 FTPS。因为服务器和打印机都在公司内网，先用普通 FTP 是一个合理的起点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;身份验证选择：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Basic Authentication
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;授权方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Specified users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仅允许用于扫描的 FTP 用户访问。&lt;/p&gt;
&lt;h2&gt;创建新的FTP专用账户&lt;/h2&gt;
&lt;p&gt;打开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Computer Management
    ↓
Local Users and Groups
    ↓
Users
    ↓
New User...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Username:
scanftp
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;建议提前规划目录结构&lt;/h2&gt;
&lt;p&gt;虽然目前只有一台打印机，但建议一开始就按照打印机或部门划分目录。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan
├── MTL-P73
├── MTL-P74
├── QC-P01
└── HR
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每台 Brother 打印机配置自己的 &lt;strong&gt;Store Directory&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Store Directory

MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样可以让后续 Power Automate 根据目录自动上传到不同的 SharePoint Library，而无需修改打印机配置，也方便未来扩展更多设备。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第一个坑：Windows Firewall&lt;/h1&gt;
&lt;p&gt;FTP 本地测试一切正常。&lt;/p&gt;
&lt;p&gt;但是客户端始终无法连接。&lt;/p&gt;
&lt;h2&gt;确认FTP服务运行，FTP Site启动，监听21端口&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Windows\system32&amp;gt; systeminfo | findstr /B /C:&quot;OS Name&quot; /C:&quot;OS Version&quot; 
OS Name:    Microsoft Windows Server 2016 Standard 
OS Version: 10.0.14393 N/A Build 14393 

PS C:\Windows\system32&amp;gt; Get-Service ftpsvc 
Status     Name     DisplayName 
------     ----     ----------- 
Running    ftpsvc   Microsoft FTP Service 

PS C:\Windows\system32&amp;gt; Get-WebSite 
Name             ID     State     Physical Path                 Bindings 
----             --     -----     -------------                 -------- 
Default Web Site 1      Started   %SystemDrive%\inetpub\wwwroot http *:80: 
Scanner FTP      2      Started   C:\FTPScan                    ftp 192.168.1.129:21: 

PS C:\Windows\system32&amp;gt; netstat -ano | findstr :21 
TCP 0.0.0.0:21 0.0.0.0:0 LISTENING 8504 
TCP 192.168.1.xx:21290 4.174.148.129:443 ESTABLISHED 8684 
TCP 192.168.1.xx:21291 4.174.148.129:443 ESTABLISHED 5624 
TCP [::]:21 [::]:0 LISTENING 8504 


&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;分析&lt;/h2&gt;
&lt;p&gt;✅ FTP 服务运行中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Running  ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ FTP Site 已启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scanner FTP   Started
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ 21 端口正在监听&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0.0.0.0:21 LISTENING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 &lt;strong&gt;服务器端已经没有明显问题&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;测试TCP连接&lt;/h3&gt;
&lt;p&gt;打开一台客户端的Powershell, 执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test-NetConnection 192.168.1.129 -Port 21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;WARNING: TCP connect to (192.168.1.129 : 21) failed 
ComputerName : 192.168.1.129 
RemoteAddress : 192.168.1.129 
RemotePort : 21 
InterfaceAlias : 
Ethernet 4 SourceAddress : 192.168.1.191 
PingSucceeded : True 
PingReplyDetails (RTT) : 1 ms 
TcpTestSucceeded : False
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;检测防火墙规则&lt;/h2&gt;
&lt;p&gt;在FTP服务器执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-NetFirewallRule -DisplayGroup &quot;FTP Server&quot; | Select DisplayName, Enabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DisplayName Enabled 
----------- ------- 
FTP Server (FTP Traffic-In) True 
FTP Server (FTP Traffic-Out) True 
FTP Server Secure (FTP SSL Traffic-In) True 
FTP Server Secure (FTP SSL Traffic-Out) True 
FTP Server Passive (FTP Passive Traffic-In) True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看起来没有问题&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE] Windows Firewall
因为这台服务器加入了AD域，也许domain的Firewall Profile 影响到了FTP，我们先关掉来测试一下&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在服务器执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-NetFirewallProfile -Profile Domain -Enabled False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722115720.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;关闭Domain Firewall Rule 之后，&lt;code&gt;TcpTestSucceeded : True&lt;/code&gt;
这说明：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;就是 Windows Defender Firewall（或者域 GPO 控制的防火墙）导致的。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;先不要一直关着防火墙&lt;/h2&gt;
&lt;p&gt;把它重新打开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-NetFirewallProfile -Profile Domain -Enabled True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们&lt;strong&gt;不要依赖 IIS 自动创建的 FTP Rule&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;直接自己创建一条明确的规则。&lt;/p&gt;
&lt;p&gt;服务器执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;New-NetFirewallRule `
    -DisplayName &quot;Scanner FTP 21&quot; `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 21 `
    -Action Allow `
    -Profile Domain
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加入这条防火墙规则之后，FTP就通了&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第二个坑：Passive Mode&lt;/h1&gt;
&lt;p&gt;解决了 21 端口以后。&lt;/p&gt;
&lt;p&gt;命令行 FTP 可以正常登录。&lt;/p&gt;
&lt;p&gt;但是 Windows Explorer 一直提示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The operation timed out.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722120132.png&quot; alt=&quot;&quot; /&gt;
经过排查发现：&lt;/p&gt;
&lt;p&gt;Windows Explorer 使用 Passive FTP。&lt;/p&gt;
&lt;p&gt;而 IIS 默认并没有配置 Passive Port。&lt;/p&gt;
&lt;h2&gt;现在配置 Passive Mode&lt;/h2&gt;
&lt;p&gt;打开 IIS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;服务器
    ↓
Scanner FTP
    ↓
点击最上面的服务器节点
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：不是 FTP Site，而是左侧树最顶端的服务器名 &lt;code&gt;PRINTER&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然后打开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FTP Firewall Support
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;应该能看到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data Channel Port Range&lt;/li&gt;
&lt;li&gt;External IP Address&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;设置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Data Channel Port Range&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;输入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;50000-50050
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;External IP Address&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;留空。&lt;/p&gt;
&lt;p&gt;（因为我们在内网，不需要填写。）&lt;/p&gt;
&lt;p&gt;点击：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Apply
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;然后放行这些端口&lt;/h2&gt;
&lt;p&gt;PowerShell：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;New-NetFirewallRule `
    -DisplayName &quot;FTP Passive Ports&quot; `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 50000-50050 `
    -Action Allow `
    -Profile Domain
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;最后一步&lt;/h2&gt;
&lt;p&gt;重启 FTP Service：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Restart-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;打印机 配置&lt;/h1&gt;
&lt;p&gt;Brother 配置非常简单。&lt;/p&gt;
&lt;p&gt;FTP Server：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;192.168.x.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Port：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Passive Mode：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Enable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Store Directory：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Username和Password填写刚才注册的本地账户&lt;/p&gt;
&lt;p&gt;这里需要注意：&lt;/p&gt;
&lt;p&gt;Store Directory 是相对于 FTP Root 的路径。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Root

C:\FTPScan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Store Directory：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终文件实际保存位置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan\MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;Power Automate&lt;/h1&gt;
&lt;p&gt;由于文件位于本地服务器。&lt;/p&gt;
&lt;p&gt;最终采用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;On-premises Data Gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连接本地文件系统。&lt;/p&gt;
&lt;p&gt;整个 Flow 非常简单：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When a file is created
        │
Get file content
        │
Create file (SharePoint)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;整个流程无需任何脚本。&lt;/p&gt;
&lt;p&gt;全部使用微软官方 Connector。&lt;/p&gt;
&lt;h2&gt;Step 1：确认 Gateway 是否已安装&lt;/h2&gt;
&lt;p&gt;先不要创建 Flow。&lt;/p&gt;
&lt;p&gt;打开服务器（PRINTER），检查是否已经安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;On-premises data gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开始菜单搜索：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果看到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On-premises data gateway&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;说明已经安装。&lt;/p&gt;
&lt;p&gt;如果没有，我们先安装。&lt;/p&gt;
&lt;p&gt;官方下载：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://aka.ms/onpremgateway&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mode&lt;/strong&gt;：Standard&lt;/li&gt;
&lt;li&gt;登录：你的 &lt;code&gt;username@contasco.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Gateway Name：例如 &lt;code&gt;PRINTER-GW&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Recovery Key：设置一个强密码并妥善保存（以后迁移或恢复 Gateway 会用到）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 2：创建 Automated Cloud Flow&lt;/h2&gt;
&lt;p&gt;打开：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://make.powerautomate.com&quot;&gt;https://make.powerautomate.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create（创建）&lt;/strong&gt; → &lt;strong&gt;Automated cloud flow&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;填写：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flow Name&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FTP Scan to SharePoint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Choose your flow&apos;s trigger&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;搜索：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;File System
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;选择：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When a file is created (properties only)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722120836.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Step 3：创建 Connection&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722120934.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;填写内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connection name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PRINTER File System&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Root folder&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;C:\FTPScan&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication Type&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Windows&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Username&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;刚才创建的本地账户&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Password&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;对应 Windows 账户密码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gateway&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;选择刚刚创建的 &lt;code&gt;PRINTER-GW&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Step 4：配置 Trigger&lt;/h2&gt;
&lt;p&gt;Trigger：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When a file is created (properties only)&lt;/strong&gt;
。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Folder&lt;/h2&gt;
&lt;p&gt;点击右边的&lt;strong&gt;文件夹图标&lt;/strong&gt;（如果有），或者填写相对于 Root Folder 的路径：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 Root Folder 已经是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 Trigger 实际监控的是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\FTPScan\MTL-P73
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722121152.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Step 5：添加读取文件内容&lt;/h2&gt;
&lt;p&gt;点击中间的 &lt;strong&gt;➕&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;选择：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Add an action&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;搜索：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;File System
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;选择：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Get file content&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在 &lt;strong&gt;Get file content → File&lt;/strong&gt; 中选择：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;body/Id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722121314.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;完成后，点击下方的 &lt;strong&gt;＋&lt;/strong&gt;，下一步添加：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SharePoint — Create file&lt;/strong&gt;
&lt;img src=&quot;Pasted%20image%2020260722121351.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;配置Action：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260722121532.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;File name 和File Content选择动态内容，接受&lt;strong&gt;Get file content&lt;/strong&gt;的输出&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;点击右上角 Save 保存 Flow。
Power automate 的最终流程:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;When a file is created (properties only)
             │
             ▼
Get file content
             │
             ▼
SharePoint Create file
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;最终效果&lt;/h1&gt;
&lt;p&gt;整个流程最终变成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;扫描
    │
    ▼
FTP
    │
    ▼
Power Automate
    │
    ▼
SharePoint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于最终用户来说。&lt;/p&gt;
&lt;p&gt;扫描流程没有任何变化。&lt;/p&gt;
&lt;p&gt;但所有文件最终都统一进入 SharePoint，享受：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;权限管理&lt;/li&gt;
&lt;li&gt;在线协作&lt;/li&gt;
&lt;li&gt;版本控制&lt;/li&gt;
&lt;li&gt;Microsoft 365 统一管理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个改造过程中，打印机无需支持 SharePoint，也无需安装任何第三方软件。&lt;/p&gt;
&lt;p&gt;这也是一种比较容易复制到其他打印机的部署方式。&lt;/p&gt;
&lt;h1&gt;Toolbox&lt;/h1&gt;
&lt;p&gt;下面整理了本次部署过程中使用到的主要 PowerShell 与 Windows 命令，可作为后续部署或排障时的参考。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 检查 FTP 服务状态&lt;/h2&gt;
&lt;p&gt;查看 IIS FTP 服务是否已经启动。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动 FTP 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Start-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;停止 FTP 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stop-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启 FTP 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Restart-Service ftpsvc
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 检查端口监听状态&lt;/h2&gt;
&lt;p&gt;确认 FTP Server 是否已经开始监听 TCP 21。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netstat -ano | findstr :21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果能够看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LISTENING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明 IIS 已经成功绑定到 21 端口。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 测试网络连通性&lt;/h2&gt;
&lt;p&gt;验证客户端是否能够访问 FTP Server。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test-NetConnection 192.168.x.x -Port 21
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重点关注：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TcpTestSucceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;True：网络连接正常&lt;/li&gt;
&lt;li&gt;False：需要继续检查防火墙或网络配置&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 测试 FTP 登录&lt;/h2&gt;
&lt;p&gt;使用 Windows 自带 FTP Client 测试登录。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ftp 192.168.x.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登录成功后可以使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dir
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看目录内容。&lt;/p&gt;
&lt;p&gt;退出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bye
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 查看 Windows Firewall 状态&lt;/h2&gt;
&lt;p&gt;查看各 Firewall Profile 是否启用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-NetFirewallProfile
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 临时关闭 Firewall（仅用于排障）&lt;/h2&gt;
&lt;p&gt;验证问题是否由防火墙导致。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-NetFirewallProfile -Profile Domain -Enabled False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恢复：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set-NetFirewallProfile -Profile Domain -Enabled True
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;不建议长期关闭防火墙，仅用于快速定位问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 创建 FTP Firewall Rule&lt;/h2&gt;
&lt;p&gt;开放 FTP 控制连接（TCP 21）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;New-NetFirewallRule `
    -DisplayName &quot;Scanner FTP&quot; `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 21 `
    -Action Allow `
    -Profile Domain
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 查看 Firewall Rules&lt;/h2&gt;
&lt;p&gt;列出 FTP 相关规则。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-NetFirewallRule |
Where-Object DisplayName -like &quot;*FTP*&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 常见排障顺序&lt;/h2&gt;
&lt;p&gt;建议按照下面的顺序进行排查，而不是同时修改多个配置：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;FTP 服务是否启动？&lt;/li&gt;
&lt;li&gt;21 端口是否监听？&lt;/li&gt;
&lt;li&gt;本机是否可以 FTP 登录？&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Test-NetConnection&lt;/code&gt; 是否成功？&lt;/li&gt;
&lt;li&gt;Firewall 是否阻挡？&lt;/li&gt;
&lt;li&gt;Passive Mode 是否配置？&lt;/li&gt;
&lt;li&gt;Brother FTP Test 是否通过？&lt;/li&gt;
&lt;li&gt;文件是否成功写入 FTP Root？&lt;/li&gt;
&lt;li&gt;Power Automate 是否触发？&lt;/li&gt;
&lt;li&gt;SharePoint 是否成功收到文件？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;按照以上流程，可以快速定位问题所在，避免盲目修改配置。&lt;/p&gt;
</content:encoded></item><item><title>一次 Windows Hello for Business (Key Trust) 的完整排障记录</title><link>https://blog.drcharon.com/posts/whfb-key-trust/zh/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/whfb-key-trust/zh/</guid><description>从 “That option is temporarily unavailable” 到成功恢复 Windows Hello for Business，这次排障几乎把 Hybrid AD、Entra ID、Intune、AD CS、Kerberos 都查了一遍。</description><pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近在部署 Windows Hello for Business 的时候，踩了一个非常大的坑。 整个过程持续了两天，最后发现并不是某一个配置出了问题，而是几个历史遗留问题叠加在一起，导致整个认证链全部失效。 这篇文章记录一下整个排障过程，也希望以后自己再遇到类似问题的时候，不用重新踩一遍。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;环境&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;本地 Active Directory&lt;/li&gt;
&lt;li&gt;Microsoft Entra ID Hybrid Join&lt;/li&gt;
&lt;li&gt;Microsoft Intune&lt;/li&gt;
&lt;li&gt;Windows Hello for Business&lt;/li&gt;
&lt;li&gt;Key Trust 部署&lt;/li&gt;
&lt;li&gt;Enterprise CA（AD CS）
Windows 11 已经完成 Hybrid Join，也已经自动加入 Intune。
理论上，只需要下发 Windows Hello Policy，就可以直接使用 PIN 登录。 事实证明，没有这么简单。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;故障现象&lt;/h1&gt;
&lt;p&gt;这次排障的起点是一个普通的用户反馈。&lt;/p&gt;
&lt;p&gt;用户在设置 Windows Hello PIN 后，原本以为以后可以直接使用 PIN 登录 Windows。&lt;/p&gt;
&lt;p&gt;结果锁屏之后，点击 PIN 登录，系统直接提示：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;该选项暂时不可用。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;请使用其他方式登录。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260717110121.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;（英文对应为：&lt;strong&gt;That option is temporarily unavailable. For now, please use a different sign-in method.&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;也就是说，PIN 已经设置成功，但是真正登录的时候却被拒绝了。&lt;/p&gt;
&lt;p&gt;与此同时，Windows Hello 设置页面并没有任何异常，也没有提示 PIN 创建失败。&lt;/p&gt;
&lt;p&gt;最开始，我怀疑是不是 PIN 本身没有创建成功。&lt;/p&gt;
&lt;p&gt;于是第一件事就是检查 Windows Hello 的状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dsregcmd /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NgcSet : NO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进一步查看 Windows Hello Prerequisite，又发现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PreReqResult : WillNotProvision
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明问题甚至还没有进入认证阶段。&lt;/p&gt;
&lt;p&gt;Windows Hello 连 Provisioning 都没有完成。&lt;/p&gt;
&lt;p&gt;于是，整个排查的第一阶段就变成了：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;为什么 Windows Hello 根本不愿意开始 Provisioning？&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;第一阶段：先让 Windows Hello 开始工作&lt;/h1&gt;
&lt;p&gt;看到 &lt;code&gt;WillNotProvision&lt;/code&gt; 之后，我决定先停下来，重新梳理一下 Windows Hello for Business 的工作流程。&lt;/p&gt;
&lt;p&gt;很多人（包括刚开始的我）都会把 Windows Hello 理解成&quot;设置一个 PIN&quot;。&lt;/p&gt;
&lt;p&gt;其实并不是。&lt;/p&gt;
&lt;p&gt;对于 Windows Hello for Business 来说，PIN 只是用来解锁本地保存的密钥，并不是用户真正的密码。&lt;/p&gt;
&lt;p&gt;整个流程大致可以分成两个阶段。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provisioning&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;第一阶段：Provisioning&lt;/h2&gt;
&lt;p&gt;第一次启用 Windows Hello 时，系统会完成一系列初始化工作，包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;检查设备是否满足部署条件；&lt;/li&gt;
&lt;li&gt;检查策略是否允许启用 Windows Hello；&lt;/li&gt;
&lt;li&gt;在 TPM（或软件密钥存储）中生成一对新的公私钥；&lt;/li&gt;
&lt;li&gt;将公钥注册到身份系统（例如 Active Directory 或 Microsoft Entra ID）；&lt;/li&gt;
&lt;li&gt;完成 Windows Hello 的初始化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只有这一切都完成之后，用户才能成功创建 PIN。&lt;/p&gt;
&lt;p&gt;如果 Provisioning 失败，那么后面的登录流程根本不会开始。&lt;/p&gt;
&lt;p&gt;而我当时看到的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PreReqResult : WillNotProvision
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正说明系统停在了这一阶段。&lt;/p&gt;
&lt;h2&gt;第二阶段：Authentication&lt;/h2&gt;
&lt;p&gt;Provisioning 完成之后，Windows Hello 才真正进入日常使用阶段。&lt;/p&gt;
&lt;p&gt;以后每次输入 PIN 登录时，Windows 会使用之前生成的密钥完成身份验证。&lt;/p&gt;
&lt;p&gt;如果这一阶段出现问题，就会表现为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PIN 可以创建；&lt;/li&gt;
&lt;li&gt;Windows Hello 已经启用；&lt;/li&gt;
&lt;li&gt;但是输入 PIN 后仍然无法登录。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而这是我后来遇到的第二个问题。&lt;/p&gt;
&lt;p&gt;我的首要目标，是先让 Windows Hello 能够完成 Provisioning。&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;检查Intune配置&lt;/h3&gt;
&lt;p&gt;Windows Hello for Business 在 Intune 里面其实有两个容易混淆的配置位置。&lt;/p&gt;
&lt;p&gt;第一个是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Devices → Windows → Enrollment → Windows Hello for Business&lt;/strong&gt;
&lt;img src=&quot;Pasted%20image%2020260717112051.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里属于 &lt;strong&gt;Enrollment&lt;/strong&gt; 配置。&lt;/p&gt;
&lt;p&gt;它决定的是设备是否启用 Windows Hello，一些简单的配置&lt;/p&gt;
&lt;p&gt;第二个是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Devices → Windows → Configuration → Policies&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260717112035.png&quot; alt=&quot;&quot; /&gt;
这里属于 &lt;strong&gt;Configuration Profile&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它的作用是向设备下发具体配置，例如 PIN 复杂度、生物识别、TPM 要求，以及其他 Windows 设置，以及使用哪一种部署模式，例如 Cloud Trust、Key Trust 或 Certificate Trust。可以把它理解成 Windows Hello 的&quot;全局策略&quot;。&lt;/p&gt;
&lt;p&gt;虽然这两个地方都会影响 Windows Hello，但职责并不一样。&lt;/p&gt;
&lt;p&gt;简单来说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enrollment&lt;/strong&gt; 决定 &lt;strong&gt;Windows Hello 能不能开始工作&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration Policy&lt;/strong&gt; 决定 &lt;strong&gt;Windows Hello 应该怎样工作&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;检查后发现，configuration profile里开启了cloud trust，而我们本地的Hybrid Domain并没有准备好。&lt;/p&gt;
&lt;p&gt;由于之前一直在测试 Cloud Trust 和 Key Trust，两种部署方式之间来回切换过不少次，因此这里的配置也经历过多次修改。&lt;/p&gt;
&lt;p&gt;继续折腾 Cloud Trust 并不是一个好的选择。&lt;/p&gt;
&lt;p&gt;我们的环境里仍然保留着 Windows Server 2012 的 Domain Controller。虽然 Cloud Trust 并不是完全不能部署，但如果要认真验证和推广，应该作为后续单独的项目来规划，而不是在修复故障的时候一边排障、一边调整整个认证架构。&lt;/p&gt;
&lt;p&gt;这次的目标其实很简单：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;先让用户能够正常使用 PIN 登录。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;于是，我决定暂时关闭 Cloud Trust，先回到目前环境更容易验证的 Key Trust 部署方式。
&lt;img src=&quot;Pasted%20image%2020260717112153.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;调整策略之后，再次查看设备状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NgcSet : dsregcmd /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要重点观察的部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+----------------------------------------------------------------------+
| Ngc Prerequisite Check                                               |
+----------------------------------------------------------------------+

            IsDeviceJoined : YES
             IsUserAzureAD : YES
             PolicyEnabled : YES
          PostLogonEnabled : YES
            DeviceEligible : YES
        SessionIsNotRemote : YES
            CertEnrollment : enrollment authority
          AdfsRefreshToken : YES
             AdfsRaIsReady : YES
    LogonCertTemplateReady : YES ( StateReady )
              PreReqResult : WillProvision
+----------------------------------------------------------------------+


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一次，用户终于可以正常创建 PIN 了。&lt;/p&gt;
&lt;p&gt;这说明 Windows Hello 已经成功完成了 Provisioning。&lt;/p&gt;
&lt;h1&gt;第二阶段：PIN 已经创建，为什么还是不能登录？&lt;/h1&gt;
&lt;p&gt;关闭 Cloud Trust 之后，Windows Hello 终于完成了 Provisioning，用户也可以正常设置 PIN。&lt;/p&gt;
&lt;p&gt;但在锁屏界面尝试使用 PIN 登录时，系统仍然提示：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;该选项暂时不可用。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;请使用其他方式登录。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这一次，问题已经和前面不同了。&lt;/p&gt;
&lt;p&gt;前面是 Windows Hello 无法开始 Provisioning；现在 PIN 已经创建成功，说明初始化流程至少已经走完了大半。&lt;/p&gt;
&lt;p&gt;接下来需要确认的是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Windows Hello 是否真的进入了 Authentication，以及认证失败发生在哪一端。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;从 Windows Hello 日志确认认证失败&lt;/h2&gt;
&lt;p&gt;我没有通过 Event Viewer 图形界面检查，而是直接使用 PowerShell 读取 Windows Hello 的 Operational 日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -LogName &quot;Microsoft-Windows-HelloForBusiness/Operational&quot; `
    -MaxEvents 50 |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TimeCreated Id LevelDisplayName Message ----------- -- ---------------- ------- 
2026-07-17 10:17:39 AM 7001 Error A user failed to sign into the device with the following information:... 
2026-07-17 10:17:39 AM 5702 Information Windows Hello wrote following protector properties to disk: HResult = 0... 
2026-07-17 10:17:38 AM 5002 Information A user is signing into the device with the following gesture informatio... 
2026-07-17 10:15:34 AM 5205 Information Windows Hello for Business on-premise authentication configurations: ... 
2026-07-17 10:12:26 AM 8045 Success Windows Hello processing completed successfully.... 
2026-07-17 10:12:26 AM 8510 Success Windows Hello key registration completed successfully. 
2026-07-17 10:12:25 AM 3510 Information Windows Hello key registration started. 2026-07-17 10:12:25 AM 8225 Success Windows Hello key creation completed successfully.... 
2026-07-17 10:12:25 AM 8067 Success Windows Hello set a certificate property on a Windows Hello key.... 
2026-07-17 10:12:24 AM 5225 Information Creating a hardware Windows Hello key with result 0x0. 
2026-07-17 10:12:24 AM 8632 Success Windows Hello for Business successfully added a user entry to the Usern... 
2026-07-17 10:12:24 AM 5205 Information Windows Hello for Business on-premise authentication configurations: ... 
2026-07-17 10:12:24 AM 5205 Information Windows Hello for Business on-premise authentication configurations: ... 
2026-07-17 10:12:24 AM 3225 Information Windows Hello key creation started. 2026-07-17 10:12:24 AM 8055 Success Windows Hello container provisioning completed successfully.... 
2026-07-17 10:12:24 AM 5702 Information Windows Hello wrote following protector properties to disk: HResult = 0... 
2026-07-17 10:12:24 AM 5702 Information Windows Hello wrote following protector properties to disk: HResult = 0... 
2026-07-17 10:12:24 AM 5225 Information Creating a software Windows Hello key with result 0x0. 2026-07-17 10:12:24 AM 5225 Information Creating a software Windows Hello key with result 0x0. 
2026-07-17 10:12:24 AM 5555 Information Windows Hello is validating that the device can satisfy all applicable ... 
2026-07-17 10:12:24 AM 5702 Information Windows Hello wrote following protector properties to disk: HResult = 0... 
2026-07-17 10:12:17 AM 5004 Information Windows Hello for Business Enabled Policy successfully enforced for the... 
2026-07-17 10:12:17 AM 3055 Information Windows Hello container provisioning started. 
2026-07-17 10:12:17 AM 6611 Warning Windows Hello could not delete the container as no container currently ... 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日志中可以看到 Windows Hello 已经成功完成了前面的处理流程，包括密钥创建和注册。
查看7001和5702 event：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -FilterHashtable @{
    LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;
    Id      = 7001
} -MaxEvents 3 |
Format-List TimeCreated, Id, LevelDisplayName, Message
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -FilterHashtable @{
    LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;
    Id      = 5205
} -MaxEvents 5 |
Format-List TimeCreated, Id, Message
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Event 7001 显示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TimeCreated : 2026-07-17 10:17:47 AM 
Id : 7001 
LevelDisplayName : 
Error Message : A user failed to sign into the device with the following information: 
Username: SYSTEM 
User SID: S-1-5-18 
Credential Type: Software Key Deployment 
Type: Key Trust 
Software Lockout Counter: 0 
Authentication Error Status: 0xC000006D 
Authentication Error Substatus: 0xC0000380
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;错误代码本身没有直接告诉我哪一项配置有问题，但它至少确认了两件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前部署类型确实是 &lt;strong&gt;Key Trust&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;失败发生在 &lt;strong&gt;Authentication&lt;/strong&gt;，而不是 Provisioning。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下来要查的是 Key Trust 认证链本身。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;确认 Windows Hello 公钥是否已经写入 AD&lt;/h2&gt;
&lt;p&gt;Key Trust 的基本逻辑是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端生成一对公私钥；&lt;/li&gt;
&lt;li&gt;私钥保存在本地，并由 PIN 解锁；&lt;/li&gt;
&lt;li&gt;公钥写入 Active Directory 用户对象；&lt;/li&gt;
&lt;li&gt;登录时，Domain Controller 使用该公钥验证客户端签名。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，我先检查用户对象的 &lt;code&gt;msDS-KeyCredentialLink&lt;/code&gt; 属性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADUser test.user `
    -Properties msDS-KeyCredentialLink |
    Select-Object -ExpandProperty msDS-KeyCredentialLink
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出中已经存在多条 Key Credential 数据。&lt;/p&gt;
&lt;p&gt;完整内容很长，类似下面这种结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;B:828:00020000200001...
B:828:00020000200001...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步非常关键。&lt;/p&gt;
&lt;p&gt;它证明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows Hello 密钥已经成功生成；&lt;/li&gt;
&lt;li&gt;公钥已经写入 Active Directory；&lt;/li&gt;
&lt;li&gt;Key Registration 已经完成；&lt;/li&gt;
&lt;li&gt;Entra Connect 和 AD 用户对象并不是当前的阻塞点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到这里，客户端该完成的工作基本都已经完成了。&lt;/p&gt;
&lt;p&gt;问题开始指向 Domain Controller。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Key Trust 为什么依赖 Domain Controller 证书？&lt;/h2&gt;
&lt;p&gt;Key Trust 虽然不需要给每个用户签发登录证书，但并不代表整个环境不需要 PKI。&lt;/p&gt;
&lt;p&gt;在混合 Key Trust 部署中，Domain Controller 上的 KDC 仍然需要一张合适的 Kerberos 证书，才能完成基于公钥的初始身份验证。&lt;/p&gt;
&lt;p&gt;换句话说：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;用户不需要证书，但 KDC 需要。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;于是，我开始检查 Domain Controller 的 KDC 日志。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -LogName &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot; `
    -MaxEvents 50 |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日志中出现了 Event 200，核心信息是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The Key Distribution Center (KDC) cannot find a suitable certificate
to use for smart card logons, or the KDC certificate could not be verified.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这就是第一条真正指向根因的证据。&lt;/p&gt;
&lt;p&gt;客户端已经有密钥，AD 里也已经有公钥，但 KDC 没有合适的证书，无法完成 Key Trust 所需要的 Kerberos 公钥认证。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;检查 Domain Controller 的证书&lt;/h2&gt;
&lt;p&gt;接下来，我分别在两台 Domain Controller 上检查本机计算机证书存储：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -store My
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果并不理想。&lt;/p&gt;
&lt;p&gt;其中一台 Domain Controller 的本机证书存储中，没有任何可以供 KDC 使用的证书。&lt;/p&gt;
&lt;p&gt;另一台虽然有一张证书，但只是历史遗留的自签名证书，并不是由当前受信任的 Enterprise CA 签发的 &lt;code&gt;Kerberos Authentication&lt;/code&gt; 证书。&lt;/p&gt;
&lt;p&gt;我也没有找到有效的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Template: Kerberos Authentication
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里，故障链条已经基本清楚了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Windows Hello Provisioning
        ↓ 成功

公钥写入 msDS-KeyCredentialLink
        ↓ 成功

客户端使用 PIN 发起 Key Trust 登录
        ↓

Domain Controller 的 KDC 没有合适的 Kerberos 证书
        ↓

Authentication Failed
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;发现已经退役的 Enterprise CA&lt;/h2&gt;
&lt;p&gt;继续检查 PKI 环境后，我发现 Active Directory 中仍然发布着旧 Enterprise CA 的对象。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CONTOSO-DC02-CA
contoso.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但对应的 CA 服务器早已退役。&lt;/p&gt;
&lt;p&gt;也就是说，Active Directory 还保留着旧 PKI 的发布信息，但已经没有一台正常工作的 Enterprise CA 可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;签发新的 Domain Controller 证书；&lt;/li&gt;
&lt;li&gt;处理自动注册；&lt;/li&gt;
&lt;li&gt;更新或续订 KDC 所需要的证书。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也解释了为什么 Domain Controller 一直没有获得有效的 &lt;code&gt;Kerberos Authentication&lt;/code&gt; 证书。&lt;/p&gt;
&lt;p&gt;问题已经不只是某一张证书过期，而是整个 Enterprise PKI 实际上已经失效。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;重新部署 Enterprise CA&lt;/h2&gt;
&lt;p&gt;由于旧 CA 已经不存在，也没有可以恢复的服务器，我决定重新部署一套 Enterprise Root CA。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新的 CA 配置为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Install-AdcsCertificationAuthority `
    -CAType EnterpriseRootCA `
    -CACommonName &quot;CONTOSO-ROOT-CA&quot; `
    -CryptoProviderName &quot;RSA#Microsoft Software Key Storage Provider&quot; `
    -KeyLength 4096 `
    -HashAlgorithmName SHA256 `
    -ValidityPeriod Years `
    -ValidityPeriodUnits 10 `
    -Force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成 AD CS 部署后，使用下面的命令确认 CA 已经能够被域环境发现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -config - -ping
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC01.contoso.local\CONTOSO-ROOT-CA
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明新的 Enterprise CA 已经正常工作。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;让 Domain Controller 自动申请证书&lt;/h2&gt;
&lt;p&gt;Enterprise CA 建立后，我重新触发了组策略和证书自动注册：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpupdate /force
certutil -pulse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后再次检查两台 Domain Controller 的本机证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -store My
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一次，两台 DC 都成功获得了新的证书，包括：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Kerberos Authentication
Domain Controller Authentication
Directory Email Replication
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;签发者为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CONTOSO-ROOT-CA
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我还确认了新的 CA 证书已经发布到 Enterprise NTAuth 存储：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -enterprise -verifystore NTAuth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出中可以看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CONTOSO-ROOT-CA
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步很重要。&lt;/p&gt;
&lt;p&gt;只有 CA 被发布并信任到 NTAuth，域控制器签发的认证证书才能被用于域身份验证。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;确认 KDC 已经加载新证书&lt;/h2&gt;
&lt;p&gt;证书申请成功后，我再次检查 KDC Operational 日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -LogName &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot; `
    -MaxEvents 50 |
    Where-Object Id -in 200, 302 |
    Select-Object TimeCreated, Id, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之前看到的是 Event 200：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;No suitable certificate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在出现了 Event 302，内容表明 KDC 已经选中了新的证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Issuer    : CONTOSO-ROOT-CA
Template  : Kerberos Authentication
Thumbprint: &amp;lt;REDACTED&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明 PKI 和 KDC 部分已经修复。&lt;/p&gt;
&lt;p&gt;到这里，Key Trust 所需要的几个关键条件都已经满足：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows Hello Provisioning 成功；&lt;/li&gt;
&lt;li&gt;公钥已经写入 Active Directory；&lt;/li&gt;
&lt;li&gt;客户端正在使用 Key Trust；&lt;/li&gt;
&lt;li&gt;Domain Controller 已获得 Kerberos Authentication 证书；&lt;/li&gt;
&lt;li&gt;KDC 已经加载并使用该证书。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;最终原因&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Intune 中启用了尚未准备好的 Cloud Trust，导致 Windows Hello 最初无法 Provision；&lt;/li&gt;
&lt;li&gt;关闭 Cloud Trust 后，Windows Hello 成功创建并注册了 Key Trust 凭据；&lt;/li&gt;
&lt;li&gt;旧 Enterprise CA 已经退役，但相关 AD 对象仍然存在；&lt;/li&gt;
&lt;li&gt;Domain Controller 没有有效的 &lt;code&gt;Kerberos Authentication&lt;/code&gt; 证书；&lt;/li&gt;
&lt;li&gt;KDC 因此无法完成 Key Trust Authentication；&lt;/li&gt;
&lt;li&gt;重建 Enterprise CA 并重新签发 DC 证书后，KDC 恢复正常；&lt;/li&gt;
&lt;li&gt;客户端仍保留着调整前的认证状态，重启后才完整刷新。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Troubleshooting Toolbox&lt;/h1&gt;
&lt;p&gt;下面是这次 Windows Hello for Business Key Trust 排障中使用到的主要命令。&lt;/p&gt;
&lt;p&gt;示例中的名称均已匿名化：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;域名：contoso.local
用户：test.user
客户端：CLIENT-01
域控制器：DC01 / DC02
企业 CA：CONTOSO-ROOT-CA
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 检查设备加入和 Windows Hello 状态&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;dsregcmd /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重点检查以下字段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AzureAdJoined
DomainJoined
DeviceAuthStatus
NgcSet
AzureAdPrt
CloudTgt
OnPremTgt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows Hello Provisioning 相关状态位于：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+----------------------------------------------------------------------+
| Ngc Prerequisite Check                                               |
+----------------------------------------------------------------------+

            IsDeviceJoined
             IsUserAzureAD
             PolicyEnabled
          PostLogonEnabled
            DeviceEligible
        SessionIsNotRemote
            CertEnrollment
          AdfsRefreshToken
             AdfsRaIsReady
    LogonCertTemplateReady
              PreReqResult
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常见结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NgcSet       : NO
PreReqResult : WillNotProvision
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示 Windows Hello 尚未完成 Provisioning。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NgcSet       : YES
PreReqResult : WillProvision
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示设备已经满足 Provisioning 条件，或者 Windows Hello 已完成初始化。&lt;/p&gt;
&lt;p&gt;需要查看更详细的设备注册过程时，可以使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dsregcmd /status /debug
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 检查 Windows Hello 策略注册表&lt;/h2&gt;
&lt;p&gt;检查 Windows Hello 是否被配置为使用 Cloud Trust：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ItemProperty `
    -Path &quot;HKLM:\SOFTWARE\Policies\Microsoft\PassportForWork&quot; `
    -ErrorAction SilentlyContinue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重点观察：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Enabled
UseCertificateForOnPremAuth
UseCloudTrustForOnPremAuth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以单独读取：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ItemPropertyValue `
    -Path &quot;HKLM:\SOFTWARE\Policies\Microsoft\PassportForWork&quot; `
    -Name &quot;UseCloudTrustForOnPremAuth&quot; `
    -ErrorAction SilentlyContinue
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Get-ItemPropertyValue `
    -Path &quot;HKLM:\SOFTWARE\Policies\Microsoft\PassportForWork&quot; `
    -Name &quot;UseCertificateForOnPremAuth&quot; `
    -ErrorAction SilentlyContinue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们最终确认的状态是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UseCloudTrustForOnPremAuth    : 0
UseCertificateForOnPremAuth  : 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;没有使用 Cloud Trust；&lt;/li&gt;
&lt;li&gt;没有使用 Certificate Trust；&lt;/li&gt;
&lt;li&gt;当前部署目标是 Key Trust。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;检查完 Intune 策略后，可以强制刷新策略：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpupdate /force
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 查询 Windows Hello Operational 日志&lt;/h2&gt;
&lt;p&gt;列出最近的 Windows Hello 事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -LogName &quot;Microsoft-Windows-HelloForBusiness/Operational&quot; `
    -MaxEvents 100 |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只查看失败或警告事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -FilterHashtable @{
        LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;
        Level   = 2, 3
    } `
    -ErrorAction SilentlyContinue |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 Authentication 事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -FilterHashtable @{
        LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;
        Id      = 7001
    } `
    -ErrorAction SilentlyContinue |
    Select-Object TimeCreated, Id, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当时看到的关键信息包括：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Deployment Type          : Key Trust
Authentication Error     : 0xC000006D
Authentication SubStatus : 0xC00002F9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面的测试中还出现过：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Authentication Error : 0xC0000380
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查询用于确认部署模式的事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -FilterHashtable @{
        LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;
        Id      = 5205
    } `
    -ErrorAction SilentlyContinue |
    Select-Object TimeCreated, Id, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Certificate Required : False
Use Cloud Trust      : False
Deployment Type      : Key Trust
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按关键词过滤日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -LogName &quot;Microsoft-Windows-HelloForBusiness/Operational&quot; `
    -MaxEvents 200 |
    Where-Object {
        $_.Message -match &quot;Authentication|Key Trust|Cloud Trust|Provision&quot;
    } |
    Select-Object TimeCreated, Id, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 检查 Windows Hello Key 是否写入 Active Directory&lt;/h2&gt;
&lt;p&gt;首先导入 Active Directory 模块：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Import-Module ActiveDirectory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查用户的 &lt;code&gt;msDS-KeyCredentialLink&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADUser test.user `
    -Properties msDS-KeyCredentialLink |
    Select-Object SamAccountName, msDS-KeyCredentialLink
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只显示 Key Credential 内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADUser test.user `
    -Properties msDS-KeyCredentialLink |
    Select-Object -ExpandProperty msDS-KeyCredentialLink
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只统计写入了多少条 Key Credential：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(
    Get-ADUser test.user `
        -Properties msDS-KeyCredentialLink
).msDS-KeyCredentialLink.Count
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出通常类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;B:828:00020000200001...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些内容很长，不需要手动解析。&lt;/p&gt;
&lt;p&gt;只要属性不是空的，就说明 Windows Hello 公钥已经成功写入用户对象。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 检查 AD 和 Forest Functional Level&lt;/h2&gt;
&lt;p&gt;检查 Forest Functional Level：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADForest |
    Select-Object Name, ForestMode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查 Domain Functional Level：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADDomain |
    Select-Object DNSRoot, DomainMode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当时环境中确认到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Forest Functional Level : Windows2008R2Forest
Domain Functional Level : Windows2012R2Domain
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 检查域控制器的本机证书&lt;/h2&gt;
&lt;p&gt;在 Domain Controller 上查看本机计算机证书存储：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -store My
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;筛选 Kerberos 相关内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -store My |
    Select-String `
        -Pattern &quot;Kerberos Authentication|Domain Controller Authentication|Issuer|Subject|Template&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 PowerShell 查看本机证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ChildItem Cert:\LocalMachine\My |
    Select-Object Subject, Issuer, Thumbprint, NotBefore, NotAfter,
        EnhancedKeyUsageList
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;筛选带有 Kerberos Authentication EKU 的证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ChildItem Cert:\LocalMachine\My |
    Where-Object {
        $_.EnhancedKeyUsageList.FriendlyName -contains &quot;Kerberos Authentication&quot;
    } |
    Select-Object Subject, Issuer, Thumbprint, NotBefore, NotAfter,
        EnhancedKeyUsageList
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查即将过期的证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ChildItem Cert:\LocalMachine\My |
    Where-Object {
        $_.NotAfter -lt (Get-Date).AddDays(60)
    } |
    Select-Object Subject, Issuer, Thumbprint, NotAfter
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 查询 KDC Operational 日志&lt;/h2&gt;
&lt;p&gt;列出最近的 KDC 事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -LogName &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot; `
    -MaxEvents 100 |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只查看 Event 200 和 Event 302：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -FilterHashtable @{
        LogName = &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot;
        Id      = 200, 302
    } `
    -ErrorAction SilentlyContinue |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Event 200 表示 KDC 找不到合适的证书，常见信息类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The Key Distribution Center cannot find a suitable certificate
to use for smart card logons, or the KDC certificate could not
be verified.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Event 302 表示 KDC 已经成功加载证书，关键字段包括：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Issuer
Template
Thumbprint
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;筛选证书相关 KDC 事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -LogName &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot; `
    -MaxEvents 200 |
    Where-Object {
        $_.Message -match &quot;certificate|Kerberos Authentication|KDC&quot;
    } |
    Select-Object TimeCreated, Id, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 检查 Enterprise CA 是否可用&lt;/h2&gt;
&lt;p&gt;列出域内可用的 Enterprise CA：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -config - -ping
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正常情况下会返回类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC01.contoso.local\CONTOSO-ROOT-CA
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 Enterprise CA 配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -config - -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查 CA 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-Service CertSvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动 CA 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Start-Service CertSvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启 CA 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Restart-Service CertSvc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查 AD CS 角色：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WindowsFeature AD-Certificate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;列出所有已安装的 AD CS 相关角色服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WindowsFeature |
    Where-Object {
        $_.Name -like &quot;ADCS*&quot;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 检查 NTAuth Enterprise Store&lt;/h2&gt;
&lt;p&gt;验证 Enterprise NTAuth 存储：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -enterprise -verifystore NTAuth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在输出中确认新的 CA：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CONTOSO-ROOT-CA
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 NTAuth 中发布的所有证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -enterprise -viewstore NTAuth
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以从 Active Directory 中读取 NTAuthCertificates 对象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$configurationNamingContext = (
    Get-ADRootDSE
).configurationNamingContext

Get-ADObject `
    -Identity &quot;CN=NTAuthCertificates,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `
    -Properties cACertificate
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;10. 检查 Active Directory 中发布的旧 CA&lt;/h2&gt;
&lt;p&gt;获取 Configuration Naming Context：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$configurationNamingContext = (
    Get-ADRootDSE
).configurationNamingContext
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 Enrollment Services 中注册的 Enterprise CA：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADObject `
    -SearchBase &quot;CN=Enrollment Services,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `
    -LDAPFilter &quot;(objectClass=pKIEnrollmentService)&quot; `
    -Properties * |
    Select-Object Name, dNSHostName, DistinguishedName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 Certification Authorities 容器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADObject `
    -SearchBase &quot;CN=Certification Authorities,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `
    -LDAPFilter &quot;(objectClass=certificationAuthority)&quot; `
    -Properties * |
    Select-Object Name, DistinguishedName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 AIA 容器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADObject `
    -SearchBase &quot;CN=AIA,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `
    -LDAPFilter &quot;(objectClass=certificationAuthority)&quot; `
    -Properties * |
    Select-Object Name, DistinguishedName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 CDP 容器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADObject `
    -SearchBase &quot;CN=CDP,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `
    -Filter * |
    Select-Object Name, ObjectClass, DistinguishedName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些命令可以帮助确认 Active Directory 中是否仍然残留已经退役的 CA 对象。&lt;/p&gt;
&lt;p&gt;不要在没有备份和影响评估的情况下直接删除这些对象。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. 触发证书自动注册&lt;/h2&gt;
&lt;p&gt;刷新计算机组策略：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpupdate /force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;触发证书自动注册：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -pulse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以本机系统账户重新触发自动注册：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -pulse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查自动注册相关事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -LogName &quot;Microsoft-Windows-CertificateServicesClient-AutoEnrollment/Operational&quot; `
    -MaxEvents 100 |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;筛选失败事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent `
    -FilterHashtable @{
        LogName = &quot;Microsoft-Windows-CertificateServicesClient-AutoEnrollment/Operational&quot;
        Level   = 2, 3
    } `
    -ErrorAction SilentlyContinue |
    Select-Object TimeCreated, Id, Message |
    Format-List
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成自动注册后，再次检查证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil -store My
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;应当能看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Kerberos Authentication
Domain Controller Authentication
Directory Email Replication
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;快速诊断顺序&lt;/h2&gt;
&lt;p&gt;遇到类似问题时，可以按照这个顺序进行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. dsregcmd /status
2. 检查 NgcSet 和 PreReqResult
3. 检查 Intune Windows Hello 策略
4. 查询 HelloForBusiness Operational 日志
5. 检查 msDS-KeyCredentialLink
6. 查询 KDC Operational 日志
7. 检查 DC 的 Kerberos Authentication 证书
8. 检查 Enterprise CA 和 NTAuth
9. 触发 DC 证书自动注册
10. 确认 KDC Event 302
11. 重启客户端并重新测试 PIN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这套顺序的重点不是把所有命令都运行一遍，而是先判断故障发生在：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Provisioning
        或
Authentication
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确定阶段之后，再继续缩小范围。&lt;/p&gt;
</content:encoded></item><item><title>Windows Hello for Business (Key Trust) Troubleshooting in a Hybrid AD Environment</title><link>https://blog.drcharon.com/posts/whfb-key-trust/en/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/whfb-key-trust/en/</guid><description>From &quot;That option is temporarily unavailable&quot; to a fully functional Windows Hello for Business deployment.</description><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, while deploying Windows Hello for Business, I ran into a much larger problem than expected. The troubleshooting process lasted two days. In the end, the issue was not caused by a single misconfiguration, but by several historical problems that had accumulated over time and broken the entire authentication chain.&lt;/p&gt;
&lt;p&gt;This article documents the full troubleshooting process so that I will not have to repeat the same investigation the next time I encounter a similar issue.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Environment&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;On-premises Active Directory&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Microsoft Entra ID Hybrid Join&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Microsoft Intune&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Windows Hello for Business&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Key Trust deployment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enterprise CA (AD CS)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Windows 11 device had already completed Hybrid Join and had been automatically enrolled in Intune.&lt;/p&gt;
&lt;p&gt;In theory, deploying a Windows Hello policy should have been enough to allow users to sign in with a PIN. In practice, it was not that simple.&lt;/p&gt;
&lt;h1&gt;Symptoms&lt;/h1&gt;
&lt;p&gt;The investigation began with a routine user report.&lt;/p&gt;
&lt;p&gt;The user had successfully configured a Windows Hello PIN and expected to use it for future Windows sign-ins.&lt;/p&gt;
&lt;p&gt;However, after locking the device and selecting PIN sign-in, Windows displayed the following message:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;That option is temporarily unavailable.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For now, please use a different sign-in method.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260717110121.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In other words, the PIN appeared to have been configured successfully, but the actual sign-in attempt was rejected.&lt;/p&gt;
&lt;p&gt;At the same time, the Windows Hello settings page showed no obvious problem and did not report that PIN creation had failed.&lt;/p&gt;
&lt;p&gt;My first assumption was that the PIN had not actually been provisioned correctly.&lt;/p&gt;
&lt;p&gt;The first step was therefore to check the Windows Hello state:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
dsregcmd /status

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result showed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
NgcSet : NO

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Further down, under the Windows Hello prerequisite check, I found:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
PreReqResult : WillNotProvision

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This meant the problem had not even reached the authentication stage.&lt;/p&gt;
&lt;p&gt;Windows Hello had not completed provisioning.&lt;/p&gt;
&lt;p&gt;The first phase of the investigation therefore became:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why was Windows Hello refusing to begin provisioning?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;Phase 1: Getting Windows Hello to Provision&lt;/h1&gt;
&lt;p&gt;After seeing &lt;code&gt;WillNotProvision&lt;/code&gt;, I stopped and reviewed how Windows Hello for Business actually works.&lt;/p&gt;
&lt;p&gt;Many people, including me at the beginning of this investigation, think of Windows Hello as simply “setting a PIN.”&lt;/p&gt;
&lt;p&gt;That is not what it is.&lt;/p&gt;
&lt;p&gt;With Windows Hello for Business, the PIN only unlocks a locally stored key. It is not the user&apos;s actual password.&lt;/p&gt;
&lt;p&gt;The process can be divided into two main stages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Provisioning&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Authentication&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Stage 1: Provisioning&lt;/h2&gt;
&lt;p&gt;When Windows Hello is enabled for the first time, Windows performs several initialization tasks, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Checking whether the device meets the deployment requirements;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Checking whether policy allows Windows Hello to be enabled;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generating a new public/private key pair in the TPM or software key storage;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Registering the public key with the identity system, such as Active Directory or Microsoft Entra ID;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Completing Windows Hello initialization.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only after all of these steps are complete can the user successfully create a PIN.&lt;/p&gt;
&lt;p&gt;If provisioning fails, the sign-in process never begins.&lt;/p&gt;
&lt;p&gt;The value I had found:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
PreReqResult : WillNotProvision

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;confirmed that the process was stopping at this stage.&lt;/p&gt;
&lt;h2&gt;Stage 2: Authentication&lt;/h2&gt;
&lt;p&gt;After provisioning is complete, Windows Hello enters its normal day-to-day use phase.&lt;/p&gt;
&lt;p&gt;Each time the user enters the PIN, Windows uses the previously generated key to authenticate the user.&lt;/p&gt;
&lt;p&gt;If this stage fails, the symptoms are different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The PIN can be created;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Windows Hello appears to be enabled;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The user still cannot sign in with the PIN.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This became the second problem later in the investigation.&lt;/p&gt;
&lt;p&gt;For now, the immediate goal was to get Windows Hello to complete provisioning.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Reviewing the Intune Configuration&lt;/h3&gt;
&lt;p&gt;Windows Hello for Business has two Intune configuration locations that are easy to confuse.&lt;/p&gt;
&lt;p&gt;The first is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Devices → Windows → Enrollment → Windows Hello for Business&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260717112051.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is the &lt;strong&gt;Enrollment&lt;/strong&gt; configuration.&lt;/p&gt;
&lt;p&gt;It controls whether Windows Hello is enabled for devices and includes several basic settings.
The second location is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Devices → Windows → Configuration → Policies&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260717112035.png&quot; alt=&quot;&quot; /&gt;
This is a &lt;strong&gt;Configuration Profile&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It applies more detailed settings to the device, such as PIN complexity, biometric settings, TPM requirements, other Windows settings, and the deployment model to use, such as Cloud Trust, Key Trust, or Certificate Trust. It can be thought of as the broader Windows Hello policy.&lt;/p&gt;
&lt;p&gt;Although both locations affect Windows Hello, they serve different purposes.&lt;/p&gt;
&lt;p&gt;In simple terms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enrollment&lt;/strong&gt; determines &lt;strong&gt;whether Windows Hello can start working&lt;/strong&gt;;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration Policy&lt;/strong&gt; determines &lt;strong&gt;how Windows Hello should work&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;The investigation showed that Cloud Trust had been enabled in the configuration profile, but the on-premises hybrid domain was not ready for it.&lt;/p&gt;
&lt;p&gt;Because Cloud Trust and Key Trust had both been tested previously, the environment had switched between the two deployment models several times, and the configuration profile had been modified repeatedly.&lt;/p&gt;
&lt;p&gt;Continuing to troubleshoot Cloud Trust was not the best choice.&lt;/p&gt;
&lt;p&gt;The environment still contained Windows Server 2012 domain controllers. Although Cloud Trust was not necessarily impossible to deploy, properly validating and rolling it out should have been treated as a separate project rather than something to redesign in the middle of an incident.&lt;/p&gt;
&lt;p&gt;The immediate objective was simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Restore working PIN sign-in for the user.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I therefore disabled Cloud Trust temporarily and returned to the Key Trust deployment model, which was easier to validate in the existing environment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260717112153.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After the policy change, I checked the device state again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
dsregcmd /status

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important section was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
+----------------------------------------------------------------------+

| Ngc Prerequisite Check                                               |

+----------------------------------------------------------------------+

  

            IsDeviceJoined : YES

             IsUserAzureAD : YES

             PolicyEnabled : YES

          PostLogonEnabled : YES

            DeviceEligible : YES

        SessionIsNotRemote : YES

            CertEnrollment : enrollment authority

          AdfsRefreshToken : YES

             AdfsRaIsReady : YES

    LogonCertTemplateReady : YES ( StateReady )

              PreReqResult : WillProvision

+----------------------------------------------------------------------+

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time, the user was finally able to create the PIN successfully.&lt;/p&gt;
&lt;p&gt;Windows Hello provisioning had completed.&lt;/p&gt;
&lt;h1&gt;Phase 2: The PIN Was Created, So Why Did Sign-In Still Fail?&lt;/h1&gt;
&lt;p&gt;After Cloud Trust was disabled, Windows Hello completed provisioning and the user was able to configure a PIN.&lt;/p&gt;
&lt;p&gt;However, when the user tried to sign in from the lock screen, Windows still displayed:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;That option is temporarily unavailable.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For now, please use a different sign-in method.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was now a different problem.&lt;/p&gt;
&lt;p&gt;Earlier, Windows Hello had been unable to begin provisioning. Now the PIN had been created, which meant the initialization process had at least progressed much further.&lt;/p&gt;
&lt;p&gt;The next question was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Had Windows Hello reached the authentication stage, and where was authentication failing?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Confirming the Authentication Failure in the Windows Hello Logs&lt;/h2&gt;
&lt;p&gt;I did not use the Event Viewer GUI. Instead, I read the Windows Hello Operational log directly with PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -LogName &quot;Microsoft-Windows-HelloForBusiness/Operational&quot; `

    -MaxEvents 50 |

    Select-Object TimeCreated, Id, LevelDisplayName, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output included:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
TimeCreated Id LevelDisplayName Message

----------- -- ---------------- -------

2026-07-17 10:17:39 AM 7001 Error A user failed to sign into the device with the following information:...

2026-07-17 10:17:39 AM 5702 Information Windows Hello wrote following protector properties to disk: HResult = 0...

2026-07-17 10:17:38 AM 5002 Information A user is signing into the device with the following gesture informatio...

2026-07-17 10:15:34 AM 5205 Information Windows Hello for Business on-premise authentication configurations: ...

2026-07-17 10:12:26 AM 8045 Success Windows Hello processing completed successfully....

2026-07-17 10:12:26 AM 8510 Success Windows Hello key registration completed successfully.

2026-07-17 10:12:25 AM 3510 Information Windows Hello key registration started.

2026-07-17 10:12:25 AM 8225 Success Windows Hello key creation completed successfully....

2026-07-17 10:12:25 AM 8067 Success Windows Hello set a certificate property on a Windows Hello key....

2026-07-17 10:12:24 AM 5225 Information Creating a hardware Windows Hello key with result 0x0.

2026-07-17 10:12:24 AM 8632 Success Windows Hello for Business successfully added a user entry to the Usern...

2026-07-17 10:12:24 AM 5205 Information Windows Hello for Business on-premise authentication configurations: ...

2026-07-17 10:12:24 AM 5205 Information Windows Hello for Business on-premise authentication configurations: ...

2026-07-17 10:12:24 AM 3225 Information Windows Hello key creation started.

2026-07-17 10:12:24 AM 8055 Success Windows Hello container provisioning completed successfully....

2026-07-17 10:12:24 AM 5702 Information Windows Hello wrote following protector properties to disk: HResult = 0...

2026-07-17 10:12:24 AM 5702 Information Windows Hello wrote following protector properties to disk: HResult = 0...

2026-07-17 10:12:24 AM 5225 Information Creating a software Windows Hello key with result 0x0.

2026-07-17 10:12:24 AM 5225 Information Creating a software Windows Hello key with result 0x0.

2026-07-17 10:12:24 AM 5555 Information Windows Hello is validating that the device can satisfy all applicable ...

2026-07-17 10:12:24 AM 5702 Information Windows Hello wrote following protector properties to disk: HResult = 0...

2026-07-17 10:12:17 AM 5004 Information Windows Hello for Business Enabled Policy successfully enforced for the...

2026-07-17 10:12:17 AM 3055 Information Windows Hello container provisioning started.

2026-07-17 10:12:17 AM 6611 Warning Windows Hello could not delete the container as no container currently ...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The log showed that Windows Hello had successfully completed the earlier stages, including key creation and key registration.&lt;/p&gt;
&lt;p&gt;I then queried Events 7001 and 5205 directly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent -FilterHashtable @{

    LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;

    Id      = 7001

} -MaxEvents 3 |

Format-List TimeCreated, Id, LevelDisplayName, Message

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent -FilterHashtable @{

    LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;

    Id      = 5205

} -MaxEvents 5 |

Format-List TimeCreated, Id, Message

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Event 7001 showed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
TimeCreated : 2026-07-17 10:17:47 AM

Id : 7001

LevelDisplayName : Error

Message : A user failed to sign into the device with the following information:

Username: SYSTEM

User SID: S-1-5-18

Credential Type: Software Key

Deployment Type: Key Trust

Software Lockout Counter: 0

Authentication Error Status: 0xC000006D

Authentication Error Substatus: 0xC0000380

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The error codes did not identify the exact configuration problem, but they confirmed two important facts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The active deployment type was &lt;strong&gt;Key Trust&lt;/strong&gt;;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The failure occurred during &lt;strong&gt;authentication&lt;/strong&gt;, not provisioning.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next step was to inspect the Key Trust authentication chain itself.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Confirming That the Windows Hello Public Key Was Written to Active Directory&lt;/h2&gt;
&lt;p&gt;The basic Key Trust workflow is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The client generates a public/private key pair;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The private key remains on the client and is unlocked by the PIN;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The public key is written to the user&apos;s Active Directory object;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;During sign-in, the domain controller uses that public key to verify the client&apos;s signature.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I therefore checked the user&apos;s &lt;code&gt;msDS-KeyCredentialLink&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADUser test.user `

    -Properties msDS-KeyCredentialLink |

    Select-Object -ExpandProperty msDS-KeyCredentialLink

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output contained multiple Key Credential entries.&lt;/p&gt;
&lt;p&gt;The complete values were very long and followed a structure similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
B:828:00020000200001...

B:828:00020000200001...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was an important result.&lt;/p&gt;
&lt;p&gt;It proved that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Windows Hello key had been generated successfully;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The public key had been written to Active Directory;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Key registration had completed;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Entra Connect and the AD user object were not the current blocking point.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this stage, the client had completed almost everything expected of it.&lt;/p&gt;
&lt;p&gt;The investigation began to point toward the domain controllers.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why Does Key Trust Depend on a Domain Controller Certificate?&lt;/h2&gt;
&lt;p&gt;Key Trust does not require each user to receive a sign-in certificate, but that does not mean the environment can operate without PKI.&lt;/p&gt;
&lt;p&gt;In a hybrid Key Trust deployment, the KDC on the domain controller still requires a suitable Kerberos certificate to complete the initial public-key-based authentication.&lt;/p&gt;
&lt;p&gt;In other words:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The user does not need a certificate, but the KDC does.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I then examined the KDC Operational log on the domain controllers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -LogName &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot; `

    -MaxEvents 50 |

    Select-Object TimeCreated, Id, LevelDisplayName, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The log contained Event 200 with the following key message:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
The Key Distribution Center (KDC) cannot find a suitable certificate

to use for smart card logons, or the KDC certificate could not be verified.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was the first piece of evidence that pointed directly toward the root cause.&lt;/p&gt;
&lt;p&gt;The client had a key, and Active Directory contained the public key, but the KDC did not have a suitable certificate and therefore could not complete the Kerberos public-key authentication required by Key Trust.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Checking the Domain Controller Certificates&lt;/h2&gt;
&lt;p&gt;I checked the Local Computer certificate store on both domain controllers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -store My

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The results were not good.&lt;/p&gt;
&lt;p&gt;One domain controller had no certificate that the KDC could use.&lt;/p&gt;
&lt;p&gt;The other had only an old self-signed certificate. It was not a valid &lt;code&gt;Kerberos Authentication&lt;/code&gt; certificate issued by a currently trusted Enterprise CA.&lt;/p&gt;
&lt;p&gt;I also could not find a valid certificate with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Template: Kerberos Authentication

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, the failure chain was becoming clear:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Windows Hello provisioning

        ↓ Successful

  

Public key written to msDS-KeyCredentialLink

        ↓ Successful

  

Client attempts Key Trust PIN sign-in

        ↓

  

The KDC on the domain controller has no suitable Kerberos certificate

        ↓

  

Authentication fails

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Discovering a Retired Enterprise CA&lt;/h2&gt;
&lt;p&gt;Further PKI investigation showed that Active Directory still contained objects for old Enterprise CAs.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
CONTOSO-DC02-CA

contoso.local

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, the corresponding CA servers had already been retired.&lt;/p&gt;
&lt;p&gt;Active Directory still contained the published PKI information, but there was no functioning Enterprise CA capable of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Issuing new domain controller certificates;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Processing certificate auto-enrollment;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Renewing or replacing the certificates required by the KDC&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This explained why the domain controllers had never received a valid &lt;code&gt;Kerberos Authentication&lt;/code&gt; certificate.&lt;/p&gt;
&lt;p&gt;The issue was no longer just one expired certificate. The Enterprise PKI itself was effectively no longer operational.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Redeploying the Enterprise CA&lt;/h2&gt;
&lt;p&gt;Because the old CA no longer existed and there was no server available to recover, I deployed a new Enterprise Root CA.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new CA was configured with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Install-AdcsCertificationAuthority `

    -CAType EnterpriseRootCA `

    -CACommonName &quot;CONTOSO-ROOT-CA&quot; `

    -CryptoProviderName &quot;RSA#Microsoft Software Key Storage Provider&quot; `

    -KeyLength 4096 `

    -HashAlgorithmName SHA256 `

    -ValidityPeriod Years `

    -ValidityPeriodUnits 10 `

    -Force

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After AD CS was installed, I verified that the CA could be discovered from the domain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -config - -ping

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command returned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
DC01.contoso.local\CONTOSO-ROOT-CA

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that the new Enterprise CA was available.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Triggering Domain Controller Certificate Enrollment&lt;/h2&gt;
&lt;p&gt;After the Enterprise CA was operational, I refreshed Group Policy and triggered certificate auto-enrollment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
gpupdate /force

certutil -pulse

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then checked the Local Computer certificate store on both domain controllers again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -store My

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time, both domain controllers had successfully obtained new certificates, including:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Kerberos Authentication

Domain Controller Authentication

Directory Email Replication

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The issuer was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
CONTOSO-ROOT-CA

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also confirmed that the new CA certificate had been published to the Enterprise NTAuth store:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -enterprise -verifystore NTAuth

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output contained:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
CONTOSO-ROOT-CA

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This step matters because a domain controller authentication certificate must chain to a CA that is trusted through NTAuth before it can be used for domain authentication.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Confirming That the KDC Loaded the New Certificate&lt;/h2&gt;
&lt;p&gt;After certificate enrollment completed, I checked the KDC Operational log again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -LogName &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot; `

    -MaxEvents 50 |

    Where-Object Id -in 200, 302 |

    Select-Object TimeCreated, Id, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Previously, Event 200 had reported:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
No suitable certificate

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now Event 302 appeared and showed that the KDC had selected the new certificate:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Issuer    : CONTOSO-ROOT-CA

Template  : Kerberos Authentication

Thumbprint: &amp;lt;REDACTED&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that the PKI and KDC portions of the authentication chain had been repaired.&lt;/p&gt;
&lt;p&gt;At this point, all major Key Trust requirements were satisfied:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Windows Hello provisioning had completed;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The public key had been written to Active Directory;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The client was using Key Trust;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The domain controllers had received Kerberos Authentication certificates;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The KDC had loaded and was using the certificate.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Root Cause&lt;/h2&gt;
&lt;p&gt;The incident was caused by several overlapping problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Cloud Trust had been enabled in Intune even though the environment was not ready for it, preventing Windows Hello from provisioning initially;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After Cloud Trust was disabled, Windows Hello successfully created and registered Key Trust credentials;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The old Enterprise CA had been retired, but related AD objects still remained;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The domain controllers did not have valid &lt;code&gt;Kerberos Authentication&lt;/code&gt; certificates;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The KDC therefore could not complete Key Trust authentication;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After the Enterprise CA was rebuilt and new domain controller certificates were issued, the KDC began functioning normally again;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The client still retained authentication state from before the repair, and a reboot was required before the corrected state was fully loaded&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Troubleshooting Toolbox&lt;/h1&gt;
&lt;p&gt;The following commands were used during this Windows Hello for Business Key Trust investigation.&lt;/p&gt;
&lt;p&gt;The names in the examples have been anonymized:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Domain: contoso.local

User: test.user

Client: CLIENT-01

Domain controllers: DC01 / DC02

Enterprise CA: CONTOSO-ROOT-CA

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;1. Check Device Join and Windows Hello Status&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;
dsregcmd /status

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Review these fields:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
AzureAdJoined

DomainJoined

DeviceAuthStatus

NgcSet

AzureAdPrt

CloudTgt

OnPremTgt

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows Hello provisioning status appears under:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
+----------------------------------------------------------------------+

| Ngc Prerequisite Check                                               |

+----------------------------------------------------------------------+

  

            IsDeviceJoined

             IsUserAzureAD

             PolicyEnabled

          PostLogonEnabled

            DeviceEligible

        SessionIsNotRemote

            CertEnrollment

          AdfsRefreshToken

             AdfsRaIsReady

    LogonCertTemplateReady

              PreReqResult

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Common results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
NgcSet       : NO

PreReqResult : WillNotProvision

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This indicates that Windows Hello provisioning has not completed.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
NgcSet       : YES

PreReqResult : WillProvision

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This indicates that the device meets the provisioning requirements or that Windows Hello has already been initialized.&lt;/p&gt;
&lt;p&gt;For more detailed device registration information:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
dsregcmd /status /debug

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Check Windows Hello Policy Registry Values&lt;/h2&gt;
&lt;p&gt;Check whether Windows Hello is configured to use Cloud Trust:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ItemProperty `

    -Path &quot;HKLM:\SOFTWARE\Policies\Microsoft\PassportForWork&quot; `

    -ErrorAction SilentlyContinue

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Review:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Enabled

UseCertificateForOnPremAuth

UseCloudTrustForOnPremAuth

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The values can also be queried individually:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ItemPropertyValue `

    -Path &quot;HKLM:\SOFTWARE\Policies\Microsoft\PassportForWork&quot; `

    -Name &quot;UseCloudTrustForOnPremAuth&quot; `

    -ErrorAction SilentlyContinue

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
Get-ItemPropertyValue `

    -Path &quot;HKLM:\SOFTWARE\Policies\Microsoft\PassportForWork&quot; `

    -Name &quot;UseCertificateForOnPremAuth&quot; `

    -ErrorAction SilentlyContinue

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final state in this case was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
UseCloudTrustForOnPremAuth   : 0

UseCertificateForOnPremAuth : 0

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Cloud Trust is not being used;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Certificate Trust is not being used;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The intended deployment model is Key Trust.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After changing the Intune policy, force a policy refresh:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
gpupdate /force

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Query the Windows Hello Operational Log&lt;/h2&gt;
&lt;p&gt;List recent Windows Hello events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -LogName &quot;Microsoft-Windows-HelloForBusiness/Operational&quot; `

    -MaxEvents 100 |

    Select-Object TimeCreated, Id, LevelDisplayName, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Show only warning and error events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -FilterHashtable @{

        LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;

        Level   = 2, 3

    } `

    -ErrorAction SilentlyContinue |

    Select-Object TimeCreated, Id, LevelDisplayName, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Query authentication failures:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -FilterHashtable @{

        LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;

        Id      = 7001

    } `

    -ErrorAction SilentlyContinue |

    Select-Object TimeCreated, Id, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important values observed during the incident included:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Deployment Type          : Key Trust

Authentication Error     : 0xC000006D

Authentication SubStatus : 0xC00002F9

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A later test also produced:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Authentication Error : 0xC0000380

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Query the event that confirms the deployment configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -FilterHashtable @{

        LogName = &quot;Microsoft-Windows-HelloForBusiness/Operational&quot;

        Id      = 5205

    } `

    -ErrorAction SilentlyContinue |

    Select-Object TimeCreated, Id, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Important values:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Certificate Required : False

Use Cloud Trust      : False

Deployment Type      : Key Trust

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Filter by relevant keywords:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -LogName &quot;Microsoft-Windows-HelloForBusiness/Operational&quot; `

    -MaxEvents 200 |

    Where-Object {

        $_.Message -match &quot;Authentication|Key Trust|Cloud Trust|Provision&quot;

    } |

    Select-Object TimeCreated, Id, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Check Whether the Windows Hello Key Was Written to Active Directory&lt;/h2&gt;
&lt;p&gt;Import the Active Directory module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Import-Module ActiveDirectory

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspect the user&apos;s &lt;code&gt;msDS-KeyCredentialLink&lt;/code&gt; attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADUser test.user `

    -Properties msDS-KeyCredentialLink |

    Select-Object SamAccountName, msDS-KeyCredentialLink

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Show only the Key Credential values:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADUser test.user `

    -Properties msDS-KeyCredentialLink |

    Select-Object -ExpandProperty msDS-KeyCredentialLink

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Count the number of Key Credential entries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
(

    Get-ADUser test.user `

        -Properties msDS-KeyCredentialLink

).msDS-KeyCredentialLink.Count

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output usually resembles:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
B:828:00020000200001...

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The values are long and normally do not need to be parsed manually.&lt;/p&gt;
&lt;p&gt;If the attribute is not empty, the Windows Hello public key has been written to the user&apos;s AD object.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Check Domain and Forest Functional Levels&lt;/h2&gt;
&lt;p&gt;Check the forest functional level:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADForest |

    Select-Object Name, ForestMode

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the domain functional level:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADDomain |

    Select-Object DNSRoot, DomainMode

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The environment in this case reported:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Forest Functional Level : Windows2008R2Forest

Domain Functional Level : Windows2012R2Domain

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Check Local Certificates on the Domain Controllers&lt;/h2&gt;
&lt;p&gt;View the Local Computer personal certificate store:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -store My

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Filter for Kerberos-related information:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -store My |

    Select-String `

        -Pattern &quot;Kerberos Authentication|Domain Controller Authentication|Issuer|Subject|Template&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspect certificates with PowerShell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ChildItem Cert:\LocalMachine\My |

    Select-Object Subject, Issuer, Thumbprint, NotBefore, NotAfter,

        EnhancedKeyUsageList

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find certificates with the Kerberos Authentication EKU:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ChildItem Cert:\LocalMachine\My |

    Where-Object {

        $_.EnhancedKeyUsageList.FriendlyName -contains &quot;Kerberos Authentication&quot;

    } |

    Select-Object Subject, Issuer, Thumbprint, NotBefore, NotAfter,

        EnhancedKeyUsageList

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find certificates that expire within 60 days:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ChildItem Cert:\LocalMachine\My |

    Where-Object {

        $_.NotAfter -lt (Get-Date).AddDays(60)

    } |

    Select-Object Subject, Issuer, Thumbprint, NotAfter

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Query the KDC Operational Log&lt;/h2&gt;
&lt;p&gt;List recent KDC events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -LogName &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot; `

    -MaxEvents 100 |

    Select-Object TimeCreated, Id, LevelDisplayName, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Show only Events 200 and 302:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -FilterHashtable @{

        LogName = &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot;

        Id      = 200, 302

    } `

    -ErrorAction SilentlyContinue |

    Select-Object TimeCreated, Id, LevelDisplayName, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Event 200 indicates that the KDC cannot find a suitable certificate. A typical message is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
The Key Distribution Center cannot find a suitable certificate

to use for smart card logons, or the KDC certificate could not

be verified.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Event 302 indicates that the KDC has successfully loaded a certificate. Important fields include:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Issuer

Template

Thumbprint

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Filter certificate-related KDC events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -LogName &quot;Microsoft-Windows-Kerberos-Key-Distribution-Center/Operational&quot; `

    -MaxEvents 200 |

    Where-Object {

        $_.Message -match &quot;certificate|Kerberos Authentication|KDC&quot;

    } |

    Select-Object TimeCreated, Id, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Check Whether the Enterprise CA Is Available&lt;/h2&gt;
&lt;p&gt;List Enterprise CAs available in the domain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -config - -ping

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A working result should resemble:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
DC01.contoso.local\CONTOSO-ROOT-CA

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View Enterprise CA configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -config - -

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the CA service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-Service CertSvc

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the CA service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Start-Service CertSvc

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart the CA service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Restart-Service CertSvc

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the AD CS role:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WindowsFeature AD-Certificate

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List installed AD CS role services:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WindowsFeature |

    Where-Object {

        $_.Name -like &quot;ADCS*&quot;

    }

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Check the Enterprise NTAuth Store&lt;/h2&gt;
&lt;p&gt;Verify the Enterprise NTAuth store:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -enterprise -verifystore NTAuth

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Confirm that the new CA appears in the output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
CONTOSO-ROOT-CA

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View all certificates published to NTAuth:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -enterprise -viewstore NTAuth

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The NTAuthCertificates object can also be read directly from Active Directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$configurationNamingContext = (

    Get-ADRootDSE

).configurationNamingContext

  

Get-ADObject `

    -Identity &quot;CN=NTAuthCertificates,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `

    -Properties cACertificate

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Check for Old CA Objects Published in Active Directory&lt;/h2&gt;
&lt;p&gt;Get the Configuration Naming Context:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
$configurationNamingContext = (

    Get-ADRootDSE

).configurationNamingContext

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List Enterprise CAs registered under Enrollment Services:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADObject `

    -SearchBase &quot;CN=Enrollment Services,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `

    -LDAPFilter &quot;(objectClass=pKIEnrollmentService)&quot; `

    -Properties * |

    Select-Object Name, dNSHostName, DistinguishedName

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspect the Certification Authorities container:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADObject `

    -SearchBase &quot;CN=Certification Authorities,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `

    -LDAPFilter &quot;(objectClass=certificationAuthority)&quot; `

    -Properties * |

    Select-Object Name, DistinguishedName

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspect the AIA container:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADObject `

    -SearchBase &quot;CN=AIA,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `

    -LDAPFilter &quot;(objectClass=certificationAuthority)&quot; `

    -Properties * |

    Select-Object Name, DistinguishedName

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspect the CDP container:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-ADObject `

    -SearchBase &quot;CN=CDP,CN=Public Key Services,CN=Services,$configurationNamingContext&quot; `

    -Filter * |

    Select-Object Name, ObjectClass, DistinguishedName

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These commands help identify objects for retired CAs that may still remain in Active Directory.&lt;/p&gt;
&lt;p&gt;Do not delete these objects without a backup and an impact assessment.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. Trigger Certificate Auto-Enrollment&lt;/h2&gt;
&lt;p&gt;Refresh computer Group Policy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
gpupdate /force

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trigger certificate auto-enrollment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -pulse

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check certificate auto-enrollment events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -LogName &quot;Microsoft-Windows-CertificateServicesClient-AutoEnrollment/Operational&quot; `

    -MaxEvents 100 |

    Select-Object TimeCreated, Id, LevelDisplayName, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Show only warning and error events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Get-WinEvent `

    -FilterHashtable @{

        LogName = &quot;Microsoft-Windows-CertificateServicesClient-AutoEnrollment/Operational&quot;

        Level   = 2, 3

    } `

    -ErrorAction SilentlyContinue |

    Select-Object TimeCreated, Id, Message |

    Format-List

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After auto-enrollment completes, check the certificate store again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
certutil -store My

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The domain controller should now have certificates such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Kerberos Authentication

Domain Controller Authentication

Directory Email Replication

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Quick Diagnostic Order&lt;/h2&gt;
&lt;p&gt;For similar incidents, use the following sequence:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
1. Run dsregcmd /status

2. Check NgcSet and PreReqResult

3. Review the Intune Windows Hello policies

4. Query the HelloForBusiness Operational log

5. Check msDS-KeyCredentialLink

6. Query the KDC Operational log

7. Check the domain controller&apos;s Kerberos Authentication certificate

8. Check the Enterprise CA and NTAuth

9. Trigger domain controller certificate auto-enrollment

10. Confirm KDC Event 302

11. Reboot the client and test PIN sign-in again

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The purpose of this sequence is not to run every command blindly. The first objective is to determine whether the failure occurs during:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Provisioning

        or

Authentication

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the failed stage is identified, the investigation can be narrowed accordingly.&lt;/p&gt;
</content:encoded></item><item><title>Complete Troubleshooting Guide for Windows Time Synchronization Issues in an Active Directory Domain</title><link>https://blog.drcharon.com/posts/dc-ntp-troubleshoot/en/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/dc-ntp-troubleshoot/en/</guid><description>A complete troubleshooting record of a non-PDC Domain Controller using Local CMOS Clock and generating Windows Time Service Event ID 129.</description><pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In Active Directory, time synchronization is a foundation for almost every authentication process.&lt;/p&gt;
&lt;p&gt;Kerberos, Active Directory replication, certificates, Group Policy, and many other components all depend on accurate time.&lt;/p&gt;
&lt;p&gt;The recommended domain time hierarchy is not complicated:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Internet NTP
     │
     ▼
PDC Emulator
     │
     ▼
Other Domain Controllers
     │
     ▼
Domain Members
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, we encountered an unusual issue in our environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The PDC Emulator was already configured to use public NTP servers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Domain Controllers could communicate with one another&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DNS was working&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Active Directory was healthy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Secure Channel was healthy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yet some Domain Controllers continued to use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final investigation showed that the issue was not with the public NTP servers, DNS, basic network connectivity, or Active Directory itself. The fault was isolated to the local Windows Time Service configuration or service state on the affected Domain Controller.&lt;/p&gt;
&lt;p&gt;This article documents the complete troubleshooting process and the reasoning that proved most useful along the way.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;NTP Fundamentals in an Active Directory Domain&lt;/h1&gt;
&lt;p&gt;Before troubleshooting, it is important to understand how time synchronization works in a Windows domain.&lt;/p&gt;
&lt;p&gt;In a workgroup environment, each Windows device can synchronize directly with a specified public NTP server. In an Active Directory environment, however, Windows normally relies on the &lt;strong&gt;domain hierarchy provided by Windows Time Service&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The Windows Time Service is named:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;W32Time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It maintains the Windows system clock and provides the time foundation required by Kerberos, Active Directory, certificate validation, and other time-sensitive services.&lt;/p&gt;
&lt;h2&gt;Why Time Matters in a Domain&lt;/h2&gt;
&lt;p&gt;Active Directory does not require every device to have exactly the same clock value, but the difference between systems must stay within an acceptable range.&lt;/p&gt;
&lt;p&gt;Kerberos is the most common example. Kerberos tickets include timestamps to help prevent replay attacks. If a client, server, and Domain Controller differ too much in time, the environment may experience:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Users being unable to sign in with domain accounts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kerberos authentication failures&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Repeated password prompts when accessing file shares&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Group Policy processing failures&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Active Directory replication or Secure Channel problems&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Certificates being treated as not yet valid or already expired&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inconsistent timestamps across logs&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Time synchronization in a domain is therefore not only about displaying the correct time in the taskbar. It is part of the authentication architecture itself.&lt;/p&gt;
&lt;h2&gt;Domain Time Hierarchy&lt;/h2&gt;
&lt;p&gt;In a single-forest, single-domain environment, the standard time hierarchy normally looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Public NTP Server
        │
        ▼
Forest Root Domain PDC Emulator
        │
        ▼
Other Domain Controllers
        │
        ▼
Domain Member Servers and Workstations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;PDC Emulator&lt;/strong&gt; in the forest root domain sits at the top of the domain time hierarchy.&lt;/p&gt;
&lt;p&gt;It should obtain time from a reliable external source, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An internal GPS clock&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A trusted network device&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A public NTP service&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other domain devices then synchronize through the Active Directory hierarchy.&lt;/p&gt;
&lt;h2&gt;The Role of the PDC Emulator&lt;/h2&gt;
&lt;p&gt;Active Directory has five FSMO roles. In addition to password updates, account lockout handling, and compatibility-related functions, the PDC Emulator also has a central role in domain time synchronization.&lt;/p&gt;
&lt;p&gt;To identify the current PDC Emulator:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netdom query fsmo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use the Active Directory PowerShell module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADDomain | Select-Object PDCEmulator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only the current PDC Emulator should normally be explicitly configured as the reliable external time source for the domain.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /config `
    /manualpeerlist:&quot;time.cloudflare.com,0x8 time.google.com,0x8&quot; `
    /syncfromflags:manual `
    /reliable:yes `
    /update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The parameters mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/manualpeerlist&lt;/code&gt;: Specifies the external NTP peers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/syncfromflags:manual&lt;/code&gt;: Uses the manually configured time sources&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/reliable:yes&lt;/code&gt;: Marks the server as a reliable time source for the domain&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/update&lt;/code&gt;: Tells Windows Time Service to reload its configuration&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After configuration, restart the service and request synchronization:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Restart-Service w32time
w32tm /resync /force
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The Difference Between &lt;code&gt;NT5DS&lt;/code&gt; and &lt;code&gt;NTP&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Use the following command to view the current synchronization mode:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The most important value is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;Type: NTP&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This means the computer uses manually specified NTP peers.&lt;/p&gt;
&lt;p&gt;This mode is commonly appropriate for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The PDC Emulator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Non-domain devices&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Special isolated systems&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Devices that cannot use the Active Directory time hierarchy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;Type: NT5DS&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This means the computer discovers a time source through the Active Directory domain hierarchy.&lt;/p&gt;
&lt;p&gt;This mode is normally appropriate for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Non-PDC Domain Controllers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain member servers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain-joined workstations&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A non-PDC server should generally not point directly to a public NTP server. It should remain configured as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Member Servers Do Not Always Synchronize Directly with the PDC&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;NT5DS&lt;/code&gt; does not mean that every domain member connects directly to the PDC Emulator.&lt;/p&gt;
&lt;p&gt;A member server may show:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source: DC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source: PDC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is not necessarily a problem.&lt;/p&gt;
&lt;p&gt;As long as DC01 ultimately receives correct time from the PDC Emulator, the following chain is valid:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Public NTP
    │
    ▼
PDC Emulator
    │
    ▼
DC01
    │
    ▼
Member Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When validating time synchronization, do not look only at whether one server points directly to the PDC. Confirm that the complete upstream chain ultimately traces back to the PDC Emulator.&lt;/p&gt;
&lt;h2&gt;Useful Diagnostic Commands&lt;/h2&gt;
&lt;p&gt;View the current time source:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View detailed synchronization status:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View the full configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View manually configured peers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /peers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the time status of all Domain Controllers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Measure the time offset from a specified source:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /stripchart /computer:PDC01.example.local /samples:5 /dataonly
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View recent Windows Time Service events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -LogName System |
    Where-Object {
        $_.ProviderName -eq &quot;Microsoft-Windows-Time-Service&quot;
    } |
    Select-Object -First 20 `
        TimeCreated,
        Id,
        LevelDisplayName,
        Message
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;Troubleshooting Environment&lt;/h1&gt;
&lt;p&gt;All server names, domain names, and IP addresses in this article have been anonymized.&lt;/p&gt;
&lt;p&gt;The affected environment was a long-running, single-forest, single-domain Active Directory deployment with four Domain Controllers:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Example Name&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Expected Time Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PDC01&lt;/td&gt;
&lt;td&gt;PDC Emulator and FSMO role holder&lt;/td&gt;
&lt;td&gt;External public NTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC01&lt;/td&gt;
&lt;td&gt;Standard Domain Controller&lt;/td&gt;
&lt;td&gt;Domain hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC02&lt;/td&gt;
&lt;td&gt;Standard Domain Controller&lt;/td&gt;
&lt;td&gt;Domain hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC03&lt;/td&gt;
&lt;td&gt;Standard Domain Controller&lt;/td&gt;
&lt;td&gt;Domain hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The domain also contained multiple Windows member servers and workstations.&lt;/p&gt;
&lt;p&gt;The expected time architecture was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;External NTP
      │
      ▼
PDC01 — PDC Emulator
      │
      ▼
DC01 / DC02 / DC03
      │
      ▼
Member Servers and Workstations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The intended configuration was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PDC01 uses &lt;code&gt;Type: NTP&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PDC01 is the only server explicitly configured as the reliable external time source&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Other Domain Controllers use &lt;code&gt;Type: NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Member servers and workstations use &lt;code&gt;Type: NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain members may synchronize with another healthy Domain Controller instead of connecting directly to the PDC&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Symptoms&lt;/h1&gt;
&lt;p&gt;On the affected Domain Controller, I first checked the current time source:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then checked the detailed status:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important values were:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stratum: 1
ReferenceId: LOCL
Source: Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This meant that the Domain Controller was not using an upstream domain time source. It was relying directly on its own hardware clock.&lt;/p&gt;
&lt;p&gt;For a non-PDC Domain Controller, this was clearly not the expected state.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 1: Identify the PDC Emulator&lt;/h1&gt;
&lt;p&gt;I first confirmed all FSMO role holders:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netdom query fsmo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output showed that all FSMO roles were located on:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PDC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The PDC role also correctly pointed to PDC01.&lt;/p&gt;
&lt;p&gt;I then used the PDC Locator directly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nltest /dsgetdc:example.local /PDC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result was similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC: \\PDC01.example.local
Flags: PDC GC DS LDAP KDC TIMESERV GTIMESERV
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that Active Directory could correctly identify the current PDC Emulator.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 2: Validate the PDC External Time Source&lt;/h1&gt;
&lt;p&gt;On PDC01, I checked the current time source:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output showed the configured public sources:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;time.cloudflare.com,time.google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then checked the synchronization status:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important values were similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Leap Indicator: 0 (no warning)
Stratum: 4
Source: time.cloudflare.com,time.google.com
Last Successful Sync Time: &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I reviewed the complete configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important values were:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NTP
AnnounceFlags: 5
NtpServer: time.cloudflare.com,time.google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These results confirmed that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The PDC Emulator could synchronize with the external NTP sources&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Windows Time Service was running correctly on the PDC&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The external NTP configuration was not the main cause of the incident&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 3: Inspect the Actual Configuration on the Affected DC&lt;/h1&gt;
&lt;p&gt;Back on DC01, I checked the full configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key result was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This meant DC01 was configured to discover a time source through the Active Directory domain hierarchy.&lt;/p&gt;
&lt;p&gt;As a non-PDC Domain Controller, it should locate an appropriate upstream domain time source. In this single-domain environment, its time chain should ultimately trace back to the PDC Emulator.&lt;/p&gt;
&lt;p&gt;However, another source check still returned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This revealed the first major contradiction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The configuration said the server should use &lt;code&gt;NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The actual runtime state showed &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For this reason, &lt;code&gt;Type: NT5DS&lt;/code&gt; alone was not enough to conclude that the server was healthy. The configuration had to be compared with the active source and synchronization status.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 4: Use &lt;code&gt;w32tm /monitor&lt;/code&gt; to Check the Entire Domain&lt;/h1&gt;
&lt;p&gt;To determine whether the problem affected only DC01, I ran:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output showed several notable conditions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC01
    RefID: LOCL
    Stratum: 1
    Offset: approximately -61 seconds

PDC01
    RefID: external NTP source
    Stratum: 4

DC02
    RefID: another external NTP source

DC03
    NTP query timeout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This suggested that the environment had more than one time configuration issue:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DC01 had fallen back to &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DC02 might still have a historical configuration that synchronized directly with a public NTP source&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DC03 did not respond to the NTP query&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only PDC01 matched the expected design&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although the incident was first noticed on DC01, the monitor output exposed historical inconsistencies across the domain time configuration.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 5: Validate the Active Directory Foundation&lt;/h1&gt;
&lt;p&gt;Because &lt;code&gt;NT5DS&lt;/code&gt; depends on Active Directory to discover a domain time peer, I next verified that DC01 could access the domain, locate the PDC, and maintain a healthy Secure Channel.&lt;/p&gt;
&lt;h2&gt;Check the Secure Channel&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Test-ComputerSecureChannel -Verbose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also verified it with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nltest /sc_verify:example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NERR_Success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that the machine account and Secure Channel were healthy.&lt;/p&gt;
&lt;h2&gt;Locate the PDC Emulator Explicitly&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;nltest /dsgetdc:example.local /PDC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC: \\PDC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that the PDC Locator worked correctly.&lt;/p&gt;
&lt;h2&gt;List All Domain Controllers&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;nltest /dclist:example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All Domain Controllers appeared in the list, and PDC01 was correctly marked as the PDC.&lt;/p&gt;
&lt;h2&gt;Confirm the FSMO Roles Again&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;netdom query fsmo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The roles still pointed correctly to PDC01.&lt;/p&gt;
&lt;h2&gt;Check DNS and Basic Connectivity&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;nslookup PDC01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The name resolved successfully.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ping PDC01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basic network connectivity was also successful.&lt;/p&gt;
&lt;p&gt;At this stage, the following components had been validated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Secure Channel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DC Locator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PDC Locator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;FSMO role ownership&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DNS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Basic network communication&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The issue still remained.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 6: Review the Windows Time Service Logs&lt;/h1&gt;
&lt;p&gt;I then reviewed Windows Time Service events in the System log:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -LogName System |
    Where-Object {
        $_.ProviderName -eq &quot;Microsoft-Windows-Time-Service&quot;
    } |
    Select-Object -First 20 `
        TimeCreated,
        Id,
        LevelDisplayName,
        Message
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The log contained Event ID 129:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NtpClient was unable to set a domain peer to use as a time source because of discovery error.

The error was:

The entry is not found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was the most important event in the investigation.&lt;/p&gt;
&lt;p&gt;It showed that the problem was not simply that Windows had selected a time source but failed to receive time data.&lt;/p&gt;
&lt;p&gt;More precisely, Windows Time Service failed during the domain peer discovery stage.&lt;/p&gt;
&lt;p&gt;In other words:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;This was not merely NTP packet loss after selecting the PDC&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;W32Time had failed to establish the Domain Peer it was supposed to use&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That distinction changed the direction of the troubleshooting process.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 7: Analyze the Local W32Time Configuration&lt;/h1&gt;
&lt;p&gt;I checked the configuration again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following combination appeared:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
AnnounceFlags: 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Type: NT5DS&lt;/code&gt; meant that the server should discover its time source through the Active Directory hierarchy.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AnnounceFlags: 5&lt;/code&gt; is commonly associated with a server advertising itself as a reliable time source.&lt;/p&gt;
&lt;p&gt;It is important to note that a Domain Controller acting as both a time client and a time server is not inherently a conflict. A DC can synchronize from an upstream server while providing time to downstream domain members.&lt;/p&gt;
&lt;p&gt;The important issue was the combination of the following facts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DC01 was not the current PDC Emulator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It was still marked as a reliable time source&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It was using only &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Windows Time Service was reporting a Domain Peer discovery failure&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This suggested that the local W32Time configuration or service state on DC01 might contain historical manual settings, remnants from a previous role configuration, or another inconsistent local state.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AnnounceFlags: 5&lt;/code&gt; alone did not prove that it was the only direct cause of Event ID 129. However, when combined with all other findings, it was reasonable to isolate the fault to the local Windows Time Service configuration or state on DC01.&lt;/p&gt;
&lt;p&gt;At this point, repeatedly running the following command was unlikely to help:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /resync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The service had not established a valid Domain Peer in the first place.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 8: Re-register Windows Time Service&lt;/h1&gt;
&lt;p&gt;Instead of adding more registry settings or NTP configuration on top of the existing state, I decided to return Windows Time Service to a cleaner default state.&lt;/p&gt;
&lt;p&gt;Stop the service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;net stop w32time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unregister Windows Time Service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /unregister
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Register it again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /register
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;net start w32time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then explicitly restore the expected domain hierarchy mode for a non-PDC Domain Controller:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /config /syncfromflags:domhier /reliable:no /update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, request synchronization and force domain rediscovery:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /resync /rediscover
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the following command does not exist as a standalone command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /rediscover
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/rediscover&lt;/code&gt; must be used as a parameter of &lt;code&gt;/resync&lt;/code&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 9: Verify the Repair&lt;/h1&gt;
&lt;p&gt;After the repair, I checked the source again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result immediately changed to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PDC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This confirmed that Windows Time Service had rediscovered the correct domain time source.&lt;/p&gt;
&lt;p&gt;I then checked:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important values to confirm were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Source&lt;/code&gt; pointed to a healthy domain time source&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Last Successful Sync Time&lt;/code&gt; had updated&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ReferenceId&lt;/code&gt; was no longer &lt;code&gt;LOCL&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Stratum&lt;/code&gt; was no longer the local reference clock value of 1&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also reviewed the complete configuration again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server still correctly showed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the non-PDC Domain Controller was no longer configured as a reliable root time source.&lt;/p&gt;
&lt;p&gt;Finally, I checked the recent events again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -LogName System |
    Where-Object {
        $_.ProviderName -eq &quot;Microsoft-Windows-Time-Service&quot;
    } |
    Select-Object -First 10 `
        TimeCreated,
        Id,
        LevelDisplayName,
        Message
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No new Event ID 129 entries continued to appear after the repair.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Step 10: Why Some Member Servers Still Synchronized with DC01&lt;/h1&gt;
&lt;p&gt;After DC01 was repaired, I checked several member servers and found that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;returned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first, this could suggest that a Group Policy had previously forced those servers to use DC01.&lt;/p&gt;
&lt;p&gt;I then checked:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important values were:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS (Local)
AnnounceFlags: 10 (Local)
NtpServer Enabled: 0 (Local)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This showed that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The server was not using a manually configured NTP peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The local NTP Server Provider was disabled&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The server was automatically selecting a source through the Active Directory hierarchy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selecting DC01 as its current time partner was normal behavior&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The actual time chain was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;External NTP
      │
      ▼
PDC01
      │
      ▼
DC01
      │
      ▼
Member Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As long as DC01 remained synchronized with PDC01, this was a normal and supported domain time hierarchy.&lt;/p&gt;
&lt;h2&gt;Additional Check: Was the Configuration Applied by Group Policy?&lt;/h2&gt;
&lt;p&gt;You can generate a Resultant Set of Policy report with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpresult /h C:\Temp\gpresult.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Search the report for:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Configure Windows NTP Client
Enable Windows NTP Client
Enable Windows NTP Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;w32tm /query /configuration&lt;/code&gt; also shows the effective source of each setting, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS (Local)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the effective configuration appeared as a local default state. There was no evidence that Group Policy had forced the member server to use a specific NTP server.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Root Cause and Evidence Boundaries&lt;/h1&gt;
&lt;p&gt;The investigation confirmed that the problem was not caused by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The PDC Emulator external NTP configuration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DNS name resolution&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Active Directory DC Locator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Secure Channel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Basic communication between Domain Controllers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fault was isolated to Windows Time Service on DC01.&lt;/p&gt;
&lt;p&gt;The DC01 configuration contained:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
AnnounceFlags: 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This showed that the effective W32Time configuration did not fully match the server&apos;s current role.&lt;/p&gt;
&lt;p&gt;However, &lt;code&gt;AnnounceFlags: 5&lt;/code&gt; alone was not enough to prove that it was the sole direct cause of Event ID 129.&lt;/p&gt;
&lt;p&gt;What could be confirmed was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;W32Time could not establish a valid Domain Peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After re-registering W32Time and restoring &lt;code&gt;domhier&lt;/code&gt;, DC01 immediately began synchronizing with PDC01&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The fault therefore existed in the local Windows Time Service configuration or service state on DC01&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Active Directory, DNS, the network, and the PDC Emulator were not the primary causes of the incident&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Final Time Synchronization Architecture&lt;/h1&gt;
&lt;p&gt;The domain should maintain the following design:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;External NTP
      │
      ▼
PDC Emulator — the only server explicitly configured with public NTP
      │
      ▼
Other Domain Controllers — NT5DS
      │
      ▼
Member Servers and Workstations — NT5DS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main rules are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Only the PDC Emulator is configured with public NTP servers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Other Domain Controllers use &lt;code&gt;NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain members use &lt;code&gt;NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Member servers may synchronize with any healthy Domain Controller&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Every time chain should ultimately trace back to the PDC Emulator&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Troubleshooting Approach Summary&lt;/h1&gt;
&lt;p&gt;The most valuable part of this incident was not the final repair command. It was the process of narrowing the fault domain step by step.&lt;/p&gt;
&lt;p&gt;When a server shows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;it is not safe to assume immediately that the public NTP service, UDP 123, or a firewall is the problem.&lt;/p&gt;
&lt;p&gt;A better approach is to identify the server&apos;s role in the domain hierarchy and determine at which stage the time source was lost.&lt;/p&gt;
&lt;h2&gt;General Troubleshooting Sequence&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Identify the device role
      │
      ▼
Confirm the PDC Emulator and FSMO roles
      │
      ▼
Validate the PDC external NTP source
      │
      ▼
Check Source, Status, and Type on the affected server
      │
      ▼
Use w32tm /monitor to inspect the entire domain
      │
      ▼
Validate Secure Channel, DNS, and PDC Locator
      │
      ▼
Review Windows Time Service events
      │
      ▼
Determine whether the failure is discovery, communication, or local configuration
      │
      ▼
Restore W32Time and rejoin the domain time hierarchy
      │
      ▼
Validate the complete synchronization chain
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Common Symptoms and What to Check First&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Symptom&lt;/td&gt;
&lt;td&gt;First Checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDC uses &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;External NTP, UDP 123, PDC configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-PDC DC uses &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NT5DS&lt;/code&gt;, DC Locator, Event ID 129&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;No time data was available&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Current peer, network connectivity, event logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard DC shows &lt;code&gt;Type: NTP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Historical manual configuration or Group Policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Member server synchronizes with a standard DC&lt;/td&gt;
&lt;td&gt;Usually normal; validate the upstream chain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w32tm /monitor&lt;/code&gt; times out&lt;/td&gt;
&lt;td&gt;W32Time service, firewall, UDP 123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RefID: LOCL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The computer is currently using its local clock&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;The Most Important Diagnostic Principle&lt;/h2&gt;
&lt;p&gt;Windows Time problems generally fall into one of the following categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration error&lt;/strong&gt;: The device uses a &lt;code&gt;Type&lt;/code&gt; or peer that does not match its role.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Discovery failure&lt;/strong&gt;: W32Time cannot establish a Domain Peer through the domain hierarchy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Communication failure&lt;/strong&gt;: A source has been selected, but time data cannot be received.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local service problem&lt;/strong&gt;: The configuration appears correct, but the runtime state does not match it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Normal domain hierarchy behavior&lt;/strong&gt;: A member server selects a standard Domain Controller instead of the PDC directly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When a server displays &lt;code&gt;Local CMOS Clock&lt;/code&gt;, the real question is not:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Which public NTP server should I configure instead?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The real question is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What is this server&apos;s role in the domain time hierarchy, and at which stage did it lose its correct time source?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Only after determining whether the failure occurred during configuration, discovery, communication, or synchronization should corrective settings be applied. Otherwise, troubleshooting may simply add another layer of historical configuration.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Command Toolbox&lt;/h1&gt;
&lt;h2&gt;Role and Domain Discovery&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;netdom query fsmo
Get-ADDomain | Select-Object PDCEmulator
nltest /dsgetdc:example.local /PDC
nltest /dclist:example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Secure Channel&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Test-ComputerSecureChannel -Verbose
nltest /sc_verify:example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Windows Time Status&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
w32tm /query /status
w32tm /query /configuration
w32tm /query /peers
w32tm /monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Time Offset Test&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;w32tm /stripchart /computer:PDC01.example.local /samples:5 /dataonly
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Event Log Review&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -LogName System |
    Where-Object {
        $_.ProviderName -eq &quot;Microsoft-Windows-Time-Service&quot;
    } |
    Select-Object -First 20 `
        TimeCreated,
        Id,
        LevelDisplayName,
        Message
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Restore Windows Time Service on a Non-PDC Domain Controller&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;net stop w32time
w32tm /unregister
w32tm /register
net start w32time
w32tm /config /syncfromflags:domhier /reliable:no /update
w32tm /resync /rediscover
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Group Policy Check&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gpresult /h C:\Temp\gpresult.html
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Windows Server 域环境中 DC 时间同步异常排障记录</title><link>https://blog.drcharon.com/posts/dc-ntp-troubleshoot/zh/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/dc-ntp-troubleshoot/zh/</guid><description>记录一次 Active Directory 域环境中 Windows Time Service 配置异常导致 Domain Controller 无法同步 PDC 时间源的完整排障过程。</description><pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;在 Active Directory 中，时间同步几乎是所有身份认证的基础。&lt;/p&gt;
&lt;p&gt;Kerberos、AD Replication、Certificate、Group Policy……几乎所有组件都依赖准确的时间。&lt;/p&gt;
&lt;p&gt;微软推荐的域时间架构并不复杂：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Internet NTP
     │
     ▼
PDC Emulator
     │
     ▼
Other Domain Controllers
     │
     ▼
Domain Members
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而，我们的环境中却出现了一个比较奇怪的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PDC Emulator 已经配置公网 NTP&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain Controller 之间可以正常通信&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DNS 正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Active Directory 正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Secure Channel 正常&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是部分 Domain Controller 却一直使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终确认，问题并不在公网 NTP 服务、DNS、基础网络通信或 Active Directory 本身，而是集中在异常 DC 本机的 Windows Time Service 配置或服务状态。&lt;/p&gt;
&lt;p&gt;本文记录完整的排障过程，以及这次问题中最值得保留的判断思路。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Active Directory 域环境中的 NTP 基础知识&lt;/h1&gt;
&lt;p&gt;在开始排查之前，需要先理解 Windows 域环境中的时间同步机制。&lt;/p&gt;
&lt;p&gt;在普通工作组环境中，每台 Windows 设备可以直接向指定的公网 NTP Server 同步时间。但在 Active Directory 域环境中，Windows 默认依赖 &lt;strong&gt;Windows Time Service 的域层级同步机制&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Windows Time Service 的服务名称是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;W32Time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它负责维护 Windows 系统时间，并为 Kerberos、Active Directory、证书验证和其他依赖时间的服务提供基础。&lt;/p&gt;
&lt;h2&gt;为什么域环境对时间敏感&lt;/h2&gt;
&lt;p&gt;Active Directory 并不要求所有设备时间完全一致，但设备之间的偏差必须处于允许范围内。&lt;/p&gt;
&lt;p&gt;其中最典型的是 Kerberos。Kerberos 使用带有时间戳的 Ticket 来防止重放攻击。如果客户端、服务器和 Domain Controller 之间存在较大的时间偏差，可能出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;用户无法登录域账户&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kerberos 身份验证失败&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;访问共享文件夹时反复要求输入密码&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Group Policy 无法正常应用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;域复制或 Secure Channel 出现异常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;证书被判断为尚未生效或已经过期&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;日志时间线无法准确对应&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，域环境中的时间同步不只是让任务栏上的时钟准确，它本身就是身份认证体系的一部分。&lt;/p&gt;
&lt;h2&gt;域时间同步层级&lt;/h2&gt;
&lt;p&gt;在单林、单域环境中，标准的时间同步链通常如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Public NTP Server
        │
        ▼
Forest Root Domain PDC Emulator
        │
        ▼
Other Domain Controllers
        │
        ▼
Domain Member Servers and Workstations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;森林根域中的 &lt;strong&gt;PDC Emulator&lt;/strong&gt; 位于整个域时间层级的顶部。&lt;/p&gt;
&lt;p&gt;它应该从可靠的外部时间源获取时间，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;企业内部 GPS Clock&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;核心网络设备提供的时间源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;公网 NTP 服务&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其他域设备则通过 Active Directory 的域层级逐级同步。&lt;/p&gt;
&lt;h2&gt;PDC Emulator 的作用&lt;/h2&gt;
&lt;p&gt;Active Directory 中有五个 FSMO Roles。PDC Emulator 除了承担密码更新、账户锁定和兼容性相关功能外，还承担域时间层级中的核心角色。&lt;/p&gt;
&lt;p&gt;确认当前 PDC Emulator：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netdom query fsmo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以使用 Active Directory PowerShell Module：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADDomain | Select-Object PDCEmulator
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有当前 PDC Emulator 应该被明确配置为外部可靠时间源。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /config `
    /manualpeerlist:&quot;time.cloudflare.com,0x8 time.google.com,0x8&quot; `
    /syncfromflags:manual `
    /reliable:yes `
    /update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参数含义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/manualpeerlist&lt;/code&gt;：指定外部 NTP Peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/syncfromflags:manual&lt;/code&gt;：使用手动配置的时间源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/reliable:yes&lt;/code&gt;：将本机标记为域内可靠时间源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/update&lt;/code&gt;：通知 Windows Time Service 重新读取配置&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;配置完成后，可以重启服务并请求同步：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Restart-Service w32time
w32tm /resync /force
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;code&gt;NT5DS&lt;/code&gt; 和 &lt;code&gt;NTP&lt;/code&gt; 的区别&lt;/h2&gt;
&lt;p&gt;使用以下命令查看当前时间同步模式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重点关注：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;Type: NTP&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;表示设备使用手动指定的 NTP Peer。&lt;/p&gt;
&lt;p&gt;这种模式通常用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PDC Emulator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;非域设备&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;特殊隔离系统&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;无法使用 AD 域层级的设备&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;Type: NT5DS&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;表示设备按照 Active Directory 域层级寻找时间源。&lt;/p&gt;
&lt;p&gt;这种模式通常用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;非 PDC Domain Controller&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain Member Server&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain-joined Workstation&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;非 PDC 服务器一般不应该直接指定公网 NTP，而应该保持：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;成员服务器不一定直接同步 PDC&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;NT5DS&lt;/code&gt; 并不表示每台域成员都会直接连接 PDC Emulator。&lt;/p&gt;
&lt;p&gt;一台成员服务器可能显示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source: DC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而不是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source: PDC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这并不一定是异常。&lt;/p&gt;
&lt;p&gt;只要 DC01 本身最终从 PDC Emulator 获取正确时间，下面的同步链就是有效的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Public NTP
    │
    ▼
PDC Emulator
    │
    ▼
DC01
    │
    ▼
Member Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，验证时间同步时，不能只看一台服务器是否直接指向 PDC，还要确认整个时间链最终能够追溯到 PDC Emulator。&lt;/p&gt;
&lt;h2&gt;常用检查命令&lt;/h2&gt;
&lt;p&gt;查看当前时间源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看详细同步状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看完整配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看手动配置的 Peer：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /peers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查所有 Domain Controller 的时间状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试与指定时间源之间的偏差：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /stripchart /computer:PDC01.example.local /samples:5 /dataonly
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看最近的 Windows Time Service 事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -LogName System |
    Where-Object {
        $_.ProviderName -eq &quot;Microsoft-Windows-Time-Service&quot;
    } |
    Select-Object -First 20 `
        TimeCreated,
        Id,
        LevelDisplayName,
        Message
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;本次排障环境&lt;/h1&gt;
&lt;p&gt;本文中的服务器名称、域名和 IP 地址均已匿名化。&lt;/p&gt;
&lt;p&gt;本次排障环境为一个已经运行多年的单林、单域 Active Directory 环境，共有四台 Domain Controller：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;示例名称&lt;/th&gt;
&lt;th&gt;角色&lt;/th&gt;
&lt;th&gt;预期时间源&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PDC01&lt;/td&gt;
&lt;td&gt;PDC Emulator，持有 FSMO Roles&lt;/td&gt;
&lt;td&gt;外部公网 NTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC01&lt;/td&gt;
&lt;td&gt;普通 Domain Controller&lt;/td&gt;
&lt;td&gt;Domain Hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC02&lt;/td&gt;
&lt;td&gt;普通 Domain Controller&lt;/td&gt;
&lt;td&gt;Domain Hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DC03&lt;/td&gt;
&lt;td&gt;普通 Domain Controller&lt;/td&gt;
&lt;td&gt;Domain Hierarchy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;域成员包括多台 Windows Member Server 和 Workstation。&lt;/p&gt;
&lt;p&gt;预期的时间同步架构为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;External NTP
      │
      ▼
PDC01 — PDC Emulator
      │
      ▼
DC01 / DC02 / DC03
      │
      ▼
Member Servers and Workstations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PDC01 使用 &lt;code&gt;Type: NTP&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PDC01 是域内唯一明确配置的可靠外部时间源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其他 DC 使用 &lt;code&gt;Type: NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Member Server 和 Workstation 使用 &lt;code&gt;Type: NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;域成员不一定直接同步 PDC，也可能同步其他健康 DC&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;故障现象&lt;/h1&gt;
&lt;p&gt;首先在异常 DC 上查看当前时间来源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续查看状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出中的关键内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Stratum: 1
ReferenceId: LOCL
Source: Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明当前 DC 没有使用域内上游时间源，而是直接使用本机硬件时钟。&lt;/p&gt;
&lt;p&gt;对于非 PDC Domain Controller，这显然不是预期状态。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第一步：确认 PDC Emulator&lt;/h1&gt;
&lt;p&gt;首先确认整个域的 FSMO Roles：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netdom query fsmo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出显示所有 FSMO Roles 均位于：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PDC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 PDC Role 也正确指向 PDC01。&lt;/p&gt;
&lt;p&gt;继续使用 PDC Locator 明确查找 PDC Emulator：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nltest /dsgetdc:example.local /PDC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC: \\PDC01.example.local
Flags: PDC GC DS LDAP KDC TIMESERV GTIMESERV
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明 Active Directory 能够正确识别当前 PDC Emulator。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第二步：验证 PDC 的外部时间源&lt;/h1&gt;
&lt;p&gt;随后在 PDC01 上检查当前时间源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出显示已配置的公网时间源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;time.cloudflare.com,time.google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续查看状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键结果类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Leap Indicator: 0 (no warning)
Stratum: 4
Source: time.cloudflare.com,time.google.com
Last Successful Sync Time: &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再查看完整配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键项为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NTP
AnnounceFlags: 5
NtpServer: time.cloudflare.com,time.google.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些结果说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PDC Emulator 已经能够同步外部 NTP&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PDC 的 Windows Time Service 正常运行&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;外部 NTP 不是本次故障的主要问题&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;第三步：检查异常 DC 的实际配置&lt;/h1&gt;
&lt;p&gt;回到异常的 DC01，查看完整配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表示 DC01 已经配置为通过 Active Directory 域层级发现时间源。&lt;/p&gt;
&lt;p&gt;作为非 PDC Domain Controller，它应该通过域层级找到合适的上游时间源。在当前单域环境中，其时间链最终应能够追溯到 PDC Emulator。&lt;/p&gt;
&lt;p&gt;但是再次查询：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仍然显示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里出现了第一个明显矛盾：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;配置声明它应该使用 &lt;code&gt;NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实际状态却使用 &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，不能只看到 &lt;code&gt;Type: NT5DS&lt;/code&gt; 就认为配置正常，还必须结合 &lt;code&gt;source&lt;/code&gt; 和 &lt;code&gt;status&lt;/code&gt; 判断实际运行状态。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第四步：使用 &lt;code&gt;w32tm /monitor&lt;/code&gt; 查看整个域&lt;/h1&gt;
&lt;p&gt;为了确认问题是否只发生在 DC01，执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果显示了多个值得注意的现象：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC01
    RefID: LOCL
    Stratum: 1
    Offset: approximately -61 seconds

PDC01
    RefID: external NTP source
    Stratum: 4

DC02
    RefID: another external NTP source

DC03
    NTP query timeout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明环境中可能不只有一个时间配置问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DC01 已经退回 &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DC02 可能仍保留直接同步公网 NTP 的历史配置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DC03 没有响应 NTP 查询&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;只有 PDC01 的状态符合预期&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，本次故障虽然最先在 DC01 上被发现，但也暴露出整个域的 Windows Time 配置可能存在历史遗留和不一致。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第五步：检查 Active Directory 基础状态&lt;/h1&gt;
&lt;p&gt;由于 &lt;code&gt;NT5DS&lt;/code&gt; 依赖 Active Directory 发现 Domain Peer，下一步需要确认 DC01 是否能够正确访问域、定位 PDC 并保持正常的 Secure Channel。&lt;/p&gt;
&lt;h2&gt;检查 Secure Channel&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Test-ComputerSecureChannel -Verbose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续验证：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nltest /sc_verify:example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NERR_Success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明机器账户和 Secure Channel 正常。&lt;/p&gt;
&lt;h2&gt;明确定位 PDC Emulator&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;nltest /dsgetdc:example.local /PDC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC: \\PDC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明 PDC Locator 正常。&lt;/p&gt;
&lt;h2&gt;列出所有 Domain Controller&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;nltest /dclist:example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;列表中能够看到所有 DC，并且 PDC01 被正确标记为 PDC。&lt;/p&gt;
&lt;h2&gt;再次确认 FSMO Roles&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;netdom query fsmo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出仍然正确指向 PDC01。&lt;/p&gt;
&lt;h2&gt;检查 DNS 和基础网络&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;nslookup PDC01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;名称能够正确解析。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ping PDC01
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;基础网络连通正常。&lt;/p&gt;
&lt;p&gt;至此可以确认：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Secure Channel 正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DC Locator 正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PDC Locator 正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;FSMO Role 正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DNS 正常&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基础网络通信正常&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;问题仍然存在。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第六步：查看 Windows Time Service 日志&lt;/h1&gt;
&lt;p&gt;随后检查 System Log 中的 Windows Time Service 事件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -LogName System |
    Where-Object {
        $_.ProviderName -eq &quot;Microsoft-Windows-Time-Service&quot;
    } |
    Select-Object -First 20 `
        TimeCreated,
        Id,
        LevelDisplayName,
        Message
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现 Event ID 129：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NtpClient was unable to set a domain peer to use as a time source because of discovery error.

The error was:

The entry is not found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是整个排障过程中最重要的一条日志。&lt;/p&gt;
&lt;p&gt;它说明故障不只是“已经选中了时间源，但没有收到时间数据”。&lt;/p&gt;
&lt;p&gt;更准确地说，Windows Time Service 在设置 Domain Peer 的发现阶段就已经失败了。&lt;/p&gt;
&lt;p&gt;换句话说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;不是已经选择 PDC 后发生 NTP 数据包丢失&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;而是 W32Time 没有正确建立应该使用的 Domain Peer&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个区别直接改变了后续排查方向。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第七步：分析本机 W32Time 配置&lt;/h1&gt;
&lt;p&gt;再次查看：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现以下组合：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
AnnounceFlags: 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Type: NT5DS&lt;/code&gt; 表示服务器应通过 Active Directory 域层级发现时间源。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;AnnounceFlags: 5&lt;/code&gt; 通常用于让服务器将自己声明为可靠时间源。&lt;/p&gt;
&lt;p&gt;需要注意：&lt;/p&gt;
&lt;p&gt;一台 Domain Controller 同时作为时间客户端和时间服务器，本身并不冲突。DC 可以从上游同步时间，同时为下游域成员提供时间。&lt;/p&gt;
&lt;p&gt;真正值得注意的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DC01 并不是当前 PDC Emulator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;它却被标记为可靠时间源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;它自身又只使用 &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Windows Time Service 同时报告 Domain Peer Discovery 失败&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这表明 DC01 的 W32Time 本地配置或服务状态可能保留了过去的手动配置、角色迁移遗留状态，或其他不一致配置。&lt;/p&gt;
&lt;p&gt;仅凭 &lt;code&gt;AnnounceFlags: 5&lt;/code&gt; 不能证明它就是 Event ID 129 的唯一直接原因，但结合当前所有现象，可以合理地把故障范围集中到 DC01 本机的 Windows Time Service。&lt;/p&gt;
&lt;p&gt;此时继续反复执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /resync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;意义有限，因为服务甚至没有正确建立 Domain Peer。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第八步：重新注册 Windows Time Service&lt;/h1&gt;
&lt;p&gt;最终决定不再继续叠加新的注册表或 NTP 配置，而是将 Windows Time Service 恢复到较干净的默认状态。&lt;/p&gt;
&lt;p&gt;停止服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;net stop w32time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注销 Windows Time Service：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /unregister
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新注册：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /register
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新启动服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;net start w32time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后明确恢复为非 PDC DC 应有的域层级模式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /config /syncfromflags:domhier /reliable:no /update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后重新同步并触发重新发现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /resync /rediscover
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意，下面这个命令并不存在：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /rediscover
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/rediscover&lt;/code&gt; 应该作为 &lt;code&gt;/resync&lt;/code&gt; 的参数使用。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第九步：验证修复结果&lt;/h1&gt;
&lt;p&gt;修复完成后再次查询：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出立即变为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PDC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明 Windows Time Service 已经重新发现了正确的域时间源。&lt;/p&gt;
&lt;p&gt;继续检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;应重点确认：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Source&lt;/code&gt; 指向健康的域时间源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Last Successful Sync Time&lt;/code&gt; 已更新&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ReferenceId&lt;/code&gt; 不再是 &lt;code&gt;LOCL&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Stratum&lt;/code&gt; 不再是本地参考时钟的 Stratum 1&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;再检查完整配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确认：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且该非 PDC DC 不再被配置为可靠根时间源。&lt;/p&gt;
&lt;p&gt;最后再次检查事件日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -LogName System |
    Where-Object {
        $_.ProviderName -eq &quot;Microsoft-Windows-Time-Service&quot;
    } |
    Select-Object -First 10 `
        TimeCreated,
        Id,
        LevelDisplayName,
        Message
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确认修复之后没有持续产生新的 Event ID 129。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;第十步：为什么部分成员服务器仍然同步 DC01&lt;/h1&gt;
&lt;p&gt;修复 DC01 后，检查部分 Member Server 时发现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DC01.example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一开始这很容易让人怀疑：是不是之前有 Group Policy 强制把服务器指向了 DC01？&lt;/p&gt;
&lt;p&gt;继续查看：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键配置为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS (Local)
AnnounceFlags: 10 (Local)
NtpServer Enabled: 0 (Local)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;该服务器没有使用手动 NTP Peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;它没有启用本机 NTP Server Provider&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;它正在通过 Active Directory 域层级自动选择时间源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当前选择 DC01 作为 Time Partner 是正常行为&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时时间链为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;External NTP
      │
      ▼
PDC01
      │
      ▼
DC01
      │
      ▼
Member Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要 DC01 自身能够稳定同步 PDC01，这就是正常且受支持的域时间同步链路。&lt;/p&gt;
&lt;p&gt;正确配置参考&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Windows\system32&amp;gt; w32tm /query /status
Leap Indicator: 0(no warning)
Stratum: 6 (secondary reference - syncd by (S)NTP)
Precision: -6 (15.625ms per tick)
Root Delay: 0.0620454s
Root Dispersion: 0.1516282s
ReferenceId: 0xC0A80107 (source IP:  192.168.1.7)
Last Successful Sync Time: 7/22/2026 2:32:00 PM
Source: DC1.example.com
Poll Interval: 10 (1024s)

PS C:\Windows\system32&amp;gt; w32tm /query /configuration
[Configuration]

EventLogFlags: 2 (Local)
AnnounceFlags: 10 (Local)
TimeJumpAuditOffset: 28800 (Local)
MinPollInterval: 6 (Local)
MaxPollInterval: 10 (Local)
MaxNegPhaseCorrection: 4294967295 (Local)
MaxPosPhaseCorrection: 4294967295 (Local)
MaxAllowedPhaseOffset: 300 (Local)

FrequencyCorrectRate: 4 (Local)
PollAdjustFactor: 5 (Local)
LargePhaseOffset: 50000000 (Local)
SpikeWatchPeriod: 900 (Local)
LocalClockDispersion: 10 (Local)
HoldPeriod: 5 (Local)
PhaseCorrectRate: 1 (Local)
UpdateInterval: 100 (Local)


[TimeProviders]

NtpClient (Local)
DllName: C:\Windows\system32\w32time.dll (Local)
Enabled: 1 (Local)
InputProvider: 1 (Local)
CrossSiteSyncFlags: 2 (Local)
AllowNonstandardModeCombinations: 1 (Local)
ResolvePeerBackoffMinutes: 15 (Local)
ResolvePeerBackoffMaxTimes: 7 (Local)
CompatibilityFlags: 2147483648 (Local)
EventLogFlags: 1 (Local)
LargeSampleSkew: 3 (Local)
SpecialPollInterval: 3600 (Local)
Type: NT5DS (Local)

VMICTimeProvider (Local)
DllName: C:\Windows\System32\vmictimeprovider.dll (Local)
Enabled: 1 (Local)
InputProvider: 1 (Local)
NtpServer (Local)
DllName: C:\Windows\system32\w32time.dll (Local)
Enabled: 0 (Local)
InputProvider: 0 (Local)

PS C:\Windows\system32&amp;gt; w32tm /query /source
DC1.example.com
PS C:\Windows\system32&amp;gt; w32tm /monitor
PDC.example.com *** PDC ***[192.168.xx.xx:123]:
    ICMP: 0ms delay
    NTP: +0.0000000s offset from PDC.example.com
        RefID: time.cloudflare.com [162.159.200.123]
        Stratum: 4
DC1.example.com[192.168.x.xx:123]:
    ICMP: 0ms delay
    NTP: -0.0063029s offset from PDC.example.com
        RefID: dc2k12.example.com [192.168.xx.xx]
        Stratum: 5
DC2.example.com[192.168.x.xx:123]:
    ICMP: 0ms delay
    NTP: +0.0046926s offset from PDC.example.com
        RefID: ntp1.torix.ca [206.108.0.131]
        Stratum: 2

Warning:
Reverse name resolution is best effort. It may not be
correct since RefID field in time packets differs across
NTP implementations and may not be using IP addresses.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;补充：如何检查是否由 GPO 配置&lt;/h2&gt;
&lt;p&gt;可以生成 Resultant Set of Policy 报告：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpresult /h C:\Temp\gpresult.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在报告中搜索：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Configure Windows NTP Client
Enable Windows NTP Client
Enable Windows NTP Server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;w32tm /query /configuration&lt;/code&gt; 也会在配置项后显示来源，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS (Local)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本次成员服务器的有效配置显示为本地默认状态，没有证据表明 GPO 将其强制指定为某一台 NTP Server。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;根因与证据边界&lt;/h1&gt;
&lt;p&gt;本次排查可以确认，问题不在以下组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PDC Emulator 的外部 NTP 配置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DNS 名称解析&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Active Directory DC Locator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Secure Channel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DC 之间的基础网络通信&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;故障集中在 DC01 本机的 Windows Time Service。&lt;/p&gt;
&lt;p&gt;DC01 的配置中同时出现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Type: NT5DS
AnnounceFlags: 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这说明服务器的 W32Time 配置存在不符合当前角色预期的状态。&lt;/p&gt;
&lt;p&gt;不过，仅凭 &lt;code&gt;AnnounceFlags: 5&lt;/code&gt; 不能证明它就是 Event ID 129 的唯一直接原因。&lt;/p&gt;
&lt;p&gt;能够确定的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;W32Time 无法正确建立 Domain Peer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重新注册 W32Time 并恢复 &lt;code&gt;domhier&lt;/code&gt; 配置后，DC01 立即开始同步 PDC01&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;因此，故障根源位于 DC01 本机的 Windows Time Service 配置或服务状态&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Active Directory、DNS、网络和 PDC Emulator 本身均不是这次故障的主要原因&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;最终时间同步架构&lt;/h1&gt;
&lt;p&gt;整个域应该保持如下配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;External NTP
      │
      ▼
PDC Emulator（唯一明确配置公网 NTP）
      │
      ▼
Other Domain Controllers（NT5DS）
      │
      ▼
Member Servers and Workstations（NT5DS）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;只有 PDC Emulator 配置公网 NTP&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其他 Domain Controller 使用 &lt;code&gt;NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Domain Member 使用 &lt;code&gt;NT5DS&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;成员服务器可以同步任意健康 DC&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所有时间链最终应追溯到 PDC Emulator&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;本次故障的排查思路总结&lt;/h1&gt;
&lt;p&gt;这次问题最值得记录的，并不是最终执行了哪一条修复命令，而是如何逐层缩小故障范围。&lt;/p&gt;
&lt;p&gt;面对：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local CMOS Clock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不能直接假设是公网 NTP、UDP 123 或防火墙故障。&lt;/p&gt;
&lt;p&gt;更有效的方法是先理解服务器在域时间层级中的角色，再判断失败发生在哪个阶段。&lt;/p&gt;
&lt;h2&gt;通用排查顺序&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;确认设备角色
      │
      ▼
确认 PDC Emulator 和 FSMO Roles
      │
      ▼
确认 PDC 外部 NTP 正常
      │
      ▼
检查故障服务器的 Source、Status 和 Type
      │
      ▼
使用 w32tm /monitor 检查全域状态
      │
      ▼
验证 Secure Channel、DNS 和 PDC Locator
      │
      ▼
检查 Windows Time Service 事件日志
      │
      ▼
判断是发现失败、通信失败还是本地配置异常
      │
      ▼
恢复 W32Time 并重新加入域时间层级
      │
      ▼
验证完整时间同步链
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;常见现象与检查方向&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;现象&lt;/th&gt;
&lt;th&gt;优先检查&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PDC 使用 &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;外部 NTP、UDP 123、PDC 配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;非 PDC DC 使用 &lt;code&gt;Local CMOS Clock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NT5DS&lt;/code&gt;、DC Locator、Event ID 129&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;No time data was available&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前 Peer、网络连通、事件日志&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;普通 DC 显示 &lt;code&gt;Type: NTP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;手动配置或 GPO 遗留&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;成员服务器同步普通 DC&lt;/td&gt;
&lt;td&gt;通常正常，检查完整上游链路&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w32tm /monitor&lt;/code&gt; 查询超时&lt;/td&gt;
&lt;td&gt;W32Time 服务、防火墙、UDP 123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RefID: LOCL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;当前设备正在使用本地时钟&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;最重要的判断原则&lt;/h2&gt;
&lt;p&gt;面对 Windows Time 问题时，需要区分以下几种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;配置错误&lt;/strong&gt;：设备使用了不符合其角色的 &lt;code&gt;Type&lt;/code&gt; 或 Peer。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发现失败&lt;/strong&gt;：W32Time 无法通过域层级建立 Domain Peer。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;通信失败&lt;/strong&gt;：已经选择时间源，但无法获得时间数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;本地服务异常&lt;/strong&gt;：配置看似正确，但实际运行状态与配置不一致。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;正常域层级行为&lt;/strong&gt;：成员服务器选择普通 DC，而不是直接选择 PDC。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;当服务器显示 &lt;code&gt;Local CMOS Clock&lt;/code&gt; 时，真正需要回答的不是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;应该换成哪个公网 NTP？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这台服务器在域时间层级中的角色是什么，它在哪一个阶段失去了正确的时间源？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;只有先判断失败发生在配置、发现、通信还是同步阶段，后续修复才不会继续叠加新的历史配置。&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;命令工具箱&lt;/h1&gt;
&lt;h2&gt;角色与域发现&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;netdom query fsmo
Get-ADDomain | Select-Object PDCEmulator
nltest /dsgetdc:example.local /PDC
nltest /dclist:example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Secure Channel&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Test-ComputerSecureChannel -Verbose
nltest /sc_verify:example.local
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Windows Time 状态&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;w32tm /query /source
w32tm /query /status
w32tm /query /configuration
w32tm /query /peers
w32tm /monitor
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;时间偏差测试&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;w32tm /stripchart /computer:PDC01.example.local /samples:5 /dataonly
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;日志检查&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Get-WinEvent -LogName System |
    Where-Object {
        $_.ProviderName -eq &quot;Microsoft-Windows-Time-Service&quot;
    } |
    Select-Object -First 20 `
        TimeCreated,
        Id,
        LevelDisplayName,
        Message
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;恢复非 PDC DC 的 Windows Time Service&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;net stop w32time
w32tm /unregister
w32tm /register
net start w32time
w32tm /config /syncfromflags:domhier /reliable:no /update
w32tm /resync /rediscover
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Group Policy 检查&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;gpresult /h C:\Temp\gpresult.html
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>使用 PowerShell 批量补全 Active Directory 用户邮箱属性</title><link>https://blog.drcharon.com/posts/set-email-in-ad-bulk/zh/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/set-email-in-ad-bulk/zh/</guid><description>根据用户的 SAMAccountName 批量补全 Active Directory 中缺失的 mail 属性。</description><pubDate>Fri, 01 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;p&gt;在一次部署支持 &lt;strong&gt;LDAP Sync&lt;/strong&gt; 的系统时，我发现不少用户同步完成后都没有邮箱信息。
检查 Active Directory 后才发现，并不是 LDAP 配置有问题，而是很多用户对象根本没有填写 &lt;strong&gt;mail&lt;/strong&gt; 属性。
这是一个历史遗留问题。多年来，公司里的 AD 用户一直由不同的 IT 人员手动创建。由于 Windows 登录和 Exchange 并不会依赖这个属性，因此即使留空，用户平时也完全不会察觉，大家也就一直没有处理。&lt;/p&gt;
&lt;p&gt;好在公司的邮箱命名规则比较统一，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;sAMAccountName&amp;gt;@yourdomain.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么就可以通过 PowerShell 根据用户的登录名自动补全缺失的邮箱属性。&lt;/p&gt;
&lt;h2&gt;PowerShell 脚本&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# 加载 Active Directory 模块
Import-Module ActiveDirectory

# 指定需要处理的 AD 组
$groupName = &quot;changeme&quot;

# 遍历组内所有用户
Get-ADGroupMember -Identity $groupName -Recursive |
    Where-Object { $_.objectClass -eq &apos;user&apos; } |
    ForEach-Object {
        $user = Get-ADUser -Identity $_ -Properties mail

        if (-not $user.mail) {
            $email = &quot;$($user.SamAccountName)@yourdomain.com&quot;
            Set-ADUser -Identity $user.SamAccountName -EmailAddress $email
            Write-Host &quot;已设置邮箱：$($user.SamAccountName) -&amp;gt; $email&quot; -ForegroundColor Green
        }
        else {
            Write-Host &quot;邮箱已存在：$($user.SamAccountName) -&amp;gt; $($user.mail)&quot; -ForegroundColor Yellow
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;脚本说明&lt;/h2&gt;
&lt;p&gt;该脚本会执行以下操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;获取指定 AD 组中的所有用户（包含嵌套组）。&lt;/li&gt;
&lt;li&gt;检查每个用户的 &lt;strong&gt;mail&lt;/strong&gt; 属性是否为空。&lt;/li&gt;
&lt;li&gt;如果为空，则使用 &lt;strong&gt;sAMAccountName&lt;/strong&gt; 自动生成邮箱地址。&lt;/li&gt;
&lt;li&gt;将生成的邮箱写入 Active Directory 的 &lt;strong&gt;mail&lt;/strong&gt; 属性。&lt;/li&gt;
&lt;li&gt;已经存在邮箱的用户将自动跳过，不会被覆盖。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;将 &lt;code&gt;changeme&lt;/code&gt; 修改为需要处理的 AD 组名称。&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;yourdomain.com&lt;/code&gt; 修改为实际使用的邮箱域名。&lt;/li&gt;
&lt;li&gt;运行脚本的账户需要具有修改 Active Directory 用户属性的权限。&lt;/li&gt;
&lt;li&gt;建议先在测试环境或少量用户上验证，再应用到生产环境。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Bulk Populate Missing Email Addresses in Active Directory</title><link>https://blog.drcharon.com/posts/set-email-in-ad-bulk/en/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/set-email-in-ad-bulk/en/</guid><description>Populate missing AD mail attributes based on the user&apos;s sAMAccountName.</description><pubDate>Fri, 01 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;p&gt;During the deployment of a system that supports &lt;strong&gt;LDAP synchronization&lt;/strong&gt;, I noticed that many users were successfully synced but had no email address associated with their accounts.&lt;/p&gt;
&lt;p&gt;After checking Active Directory, I found that the issue wasn&apos;t with the LDAP configuration at all. Instead, many user objects simply had an empty &lt;strong&gt;mail&lt;/strong&gt; attribute.&lt;/p&gt;
&lt;p&gt;This turned out to be a historical issue. Over the years, AD accounts had been created manually by different IT staff members. Since neither Windows logon nor Exchange depends on the &lt;code&gt;mail&lt;/code&gt; attribute, users could work normally even if it was left blank, so the missing values went unnoticed for a long time.&lt;/p&gt;
&lt;p&gt;Fortunately, our organization follows a consistent email naming convention, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;sAMAccountName&amp;gt;@yourdomain.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With a predictable naming convention, it&apos;s easy to populate the missing &lt;strong&gt;mail&lt;/strong&gt; attributes automatically using PowerShell.&lt;/p&gt;
&lt;h2&gt;PowerShell Script&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# Load Active Directory module if not already loaded
Import-Module ActiveDirectory

# Define the group name
$groupName = &quot;changeme&quot;

# Process users and update missing emails
Get-ADGroupMember -Identity $groupName -Recursive |
    Where-Object { $_.objectClass -eq &apos;user&apos; } |
    ForEach-Object {
        $user = Get-ADUser -Identity $_ -Properties mail

        if (-not $user.mail) {
            $email = &quot;$($user.SamAccountName)@yourdomain.com&quot;
            Set-ADUser -Identity $user.SamAccountName -EmailAddress $email
            Write-Host &quot;Set email for $($user.SamAccountName): $email&quot; -ForegroundColor Green
        }
        else {
            Write-Host &quot;Email already set for $($user.SamAccountName): $($user.mail)&quot; -ForegroundColor Yellow
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What the Script Does&lt;/h2&gt;
&lt;p&gt;The script performs the following actions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Retrieves all users from the specified AD group, including nested groups.&lt;/li&gt;
&lt;li&gt;Checks whether the &lt;strong&gt;mail&lt;/strong&gt; attribute is empty.&lt;/li&gt;
&lt;li&gt;If the attribute is missing, generates an email address using the user&apos;s &lt;strong&gt;sAMAccountName&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Writes the generated email address back to the &lt;strong&gt;mail&lt;/strong&gt; attribute in Active Directory.&lt;/li&gt;
&lt;li&gt;Skips users who already have an email address configured, ensuring that existing values are not overwritten.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;changeme&lt;/code&gt; with the target Active Directory group.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;yourdomain.com&lt;/code&gt; with your organization&apos;s email domain.&lt;/li&gt;
&lt;li&gt;The account running the script must have permission to modify Active Directory user objects.&lt;/li&gt;
&lt;li&gt;Always test the script against a small pilot group before running it in a production environment.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Systemd Quick Reference</title><link>https://blog.drcharon.com/posts/systemd-note/en/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/systemd-note/en/</guid><description>A practical systemd cheat sheet covering common commands, custom services, unit files, logs, troubleshooting, and package ownership.</description><pubDate>Sun, 28 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;hr /&gt;
&lt;h2&gt;Common Commands&lt;/h2&gt;
&lt;p&gt;List all service units that are configured to start automatically at boot.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl list-unit-files --type=service --state=enabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List all currently running services.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl list-units --type=service --state=running
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check whether a service is enabled at boot and whether it is currently running.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl is-enabled nginx.service
systemctl is-active nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start, stop, restart, enable, and check the status of a service.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl enable nginx
systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Create a Custom Spring Boot Service&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;lt;&amp;lt;&apos;EOF&apos; &amp;gt;/etc/systemd/system/hello.service
[Unit]
Description=Spring Boot HelloWorld
After=syslog.target
After=network.target

[Service]
User=username
Type=simple
ExecStart=/usr/bin/java -jar /root/hello.jar
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=helloworld

[Install]
WantedBy=multi-user.target
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Default Locations of systemd Unit Files&lt;/h2&gt;
&lt;p&gt;(See &lt;code&gt;man systemd.unit&lt;/code&gt; for more details.)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Table 1. Load path when running in system mode (--system).

┌────────────────────┬─────────────────────────────┐
│ Path               │ Description                 │
├────────────────────┼─────────────────────────────┤
│ /etc/systemd/system│ Local configuration         │
├────────────────────┼─────────────────────────────┤
│ /run/systemd/system│ Runtime units               │
├────────────────────┼─────────────────────────────┤
│ /lib/systemd/system│ Units of installed packages │
└────────────────────┴─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/systemd/system&lt;/code&gt; contains locally created or customized unit files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/lib/systemd/system&lt;/code&gt; (or &lt;code&gt;/usr/lib/systemd/system&lt;/code&gt; on many modern distributions) contains unit files installed by software packages.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/run/systemd/system&lt;/code&gt; is used for runtime-generated or temporary unit files.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Reload systemd After Modifying a Unit&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever you create or modify a unit file, you should reload the systemd manager.&lt;/p&gt;
&lt;p&gt;Reloading the configuration does &lt;strong&gt;not&lt;/strong&gt; restart the service automatically, so you will usually need to run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart hello.service
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Verify the Syntax of a Unit File&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;systemd-analyze verify /etc/systemd/system/hello.service
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;List Failed Units&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;systemctl --failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List only failed services.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl --failed --type=service
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;View Service Logs&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;journalctl -u hello.service
journalctl -u hello.service -b
journalctl -u hello.service -f
journalctl -u hello.service --since &quot;30 minutes ago&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Clear the Failed State&lt;/h2&gt;
&lt;p&gt;If a service still shows as failed after fixing the issue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl reset-failed hello.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then start the service again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl start hello.service
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Use Drop-in Overrides Instead of Editing Package Files&lt;/h2&gt;
&lt;p&gt;Avoid modifying package-managed unit files directly, such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/lib/systemd/system/nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your changes may be overwritten during package upgrades.&lt;/p&gt;
&lt;p&gt;Instead, create a drop-in override:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl edit nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Service]
Restart=on-failure
RestartSec=5s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;View the merged configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl cat nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remove all local overrides and restore the original configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl revert nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Find Which Package Installed a Unit File&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;dpkg-query -S /lib/systemd/system/* | sort -u
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dpkg-query: no path found matching pattern /lib/systemd/system/ecs_mq.service
dpkg-query: no path found matching pattern /lib/systemd/system/SplunkForwarder.service
dpkg-query: no path found matching pattern /lib/systemd/system/system-systemd\x2dcryptsetup.slice
accountsservice: /lib/systemd/system/accounts-daemon.service
apparmor: /lib/systemd/system/apparmor.service
apt: /lib/systemd/system/apt-daily.service
apt: /lib/systemd/system/apt-daily.timer
apt: /lib/systemd/system/apt-daily-upgrade.service
apt: /lib/systemd/system/apt-daily-upgrade.timer
at: /lib/systemd/system/atd.service
auditd: /lib/systemd/system/auditd.service
base-files: /lib/systemd/system/motd-news.service
base-files: /lib/systemd/system/motd-news.timer
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;dpkg-query&lt;/code&gt; cannot find a matching package, it simply means that no package recorded ownership of that path in the dpkg database. The unit file may have been created manually, installed by a third-party installer, generated by a package installation script, or generated dynamically by a systemd generator. Installing a package manually with &lt;code&gt;dpkg -i&lt;/code&gt; &lt;strong&gt;does not&lt;/strong&gt; normally prevent &lt;code&gt;dpkg-query -S&lt;/code&gt; from identifying files that belong to that package.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>systemd 使用笔记</title><link>https://blog.drcharon.com/posts/systemd-note/zh/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/systemd-note/zh/</guid><description>一份实用的 systemd 速查笔记，涵盖常用命令、自定义服务、unit 文件位置、日志查看、故障排查以及软件包归属查询。</description><pubDate>Sun, 28 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;常用命令&lt;/h2&gt;
&lt;p&gt;列出已配置为开机启用的 service unit&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl list-unit-files --type=service --state=enabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看当前正在运行的服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl list-units --type=service --state=running
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分别判断开机启动和当前运行状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl is-enabled nginx.service
systemctl is-active nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开启，关闭，重启，设为开机自启，查看状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl enable nginx
systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建一个自定义的Springboot service&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;lt;&amp;lt;&apos;EOF&apos; &amp;gt;/etc/systemd/system/hello.service
[Unit]
Description=Spring Boot HelloWorld
After=syslog.target
After=network.target

[Service]
User=username
Type=simple
ExecStart=/usr/bin/java -jar /root/hello.jar
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=helloworld

[Install]
WantedBy=multi-user.target
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;service文件的位置(详情参考 man systemd.unit)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; Table 1.  Load path when running in system mode (--system).
       ┌────────────────────┬─────────────────────────────┐
       │Path                │ Description                 │
       ├────────────────────┼─────────────────────────────┤
       │/etc/systemd/system │ Local configuration         │
       ├────────────────────┼─────────────────────────────┤
       │/run/systemd/system │ Runtime units               │
       ├────────────────────┼─────────────────────────────┤
       │/lib/systemd/system │ Units of installed packages │
       └────────────────────┴─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE] 注释
&lt;code&gt;/etc/systemd/system&lt;/code&gt;文件夹里的是本地创建的服务。
&lt;code&gt;/lib/systemd/system&lt;/code&gt;路径里的是软件包创建的服务。（&lt;code&gt;/usr/lib/systemd/system&lt;/code&gt;）
&lt;code&gt;/run/systemd/system&lt;/code&gt; 用于运行时临时配置&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;修改 unit 后重新加载&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只要创建或修改了 unit 文件，就应该运行它。它不会自动重启服务，通常还需要：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart hello.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;检查 unit 文件语法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;systemd-analyze verify /etc/systemd/system/hello.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;查看失败的 unit&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;systemctl --failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仅查看失败的服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl --failed --type=service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;查看服务日志&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;journalctl -u hello.service
journalctl -u hello.service -b
journalctl -u hello.service -f
journalctl -u hello.service --since &quot;30 minutes ago&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;清除 failed 状态&lt;/h2&gt;
&lt;p&gt;修复问题后，如果服务仍显示 failed：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl reset-failed hello.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后重新启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl start hello.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;推荐使用 drop-in，而不是修改软件包文件&lt;/h2&gt;
&lt;p&gt;不要直接修改：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/lib/systemd/system/nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为软件升级可能覆盖修改。&lt;/p&gt;
&lt;p&gt;使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl edit nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Service]
Restart=on-failure
RestartSec=5s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看合并后的结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl cat nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恢复并删除本地覆盖：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl revert nginx.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;查看/lib/systemd/system路径下的服务是由什么软件包创建的&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;dpkg-query -S /lib/systemd/system/* | sort -u 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dpkg-query: no path found matching pattern /lib/systemd/system/ecs_mq.service
dpkg-query: no path found matching pattern /lib/systemd/system/SplunkForwarder.service
dpkg-query: no path found matching pattern /lib/systemd/system/system-systemd\x2dcryptsetup.slice
accountsservice: /lib/systemd/system/accounts-daemon.service
apparmor: /lib/systemd/system/apparmor.service
apt: /lib/systemd/system/apt-daily.service
apt: /lib/systemd/system/apt-daily.timer
apt: /lib/systemd/system/apt-daily-upgrade.service
apt: /lib/systemd/system/apt-daily-upgrade.timer
at: /lib/systemd/system/atd.service
auditd: /lib/systemd/system/auditd.service
base-files: /lib/systemd/system/motd-news.service
base-files: /lib/systemd/system/motd-news.timer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;aside&amp;gt;&lt;br /&gt;
💡 可以看到dpkg找不到前三个服务对应的软件包。这可能是因为这些服务的由deb文件手动安装的，比如所splunk forwarder&lt;/p&gt;
&lt;p&gt;&amp;lt;/aside&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE] 注释
&lt;code&gt;dpkg-query&lt;/code&gt; 找不到这些文件，表示 dpkg 数据库中没有软件包声明拥有该路径。它们可能是手工创建、由第三方安装脚本创建、由软件包安装脚本动态生成，或由 systemd generator 生成。仅仅使用 &lt;code&gt;dpkg -i&lt;/code&gt; 手动安装 &lt;code&gt;.deb&lt;/code&gt;，通常不会导致 &lt;code&gt;dpkg-query -S&lt;/code&gt; 无法找到包内文件。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Linux auditd Cheat Sheet</title><link>https://blog.drcharon.com/posts/auditd-note/en/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/auditd-note/en/</guid><description>Quick reference for Linux auditd configuration, audit rules, file watches, and the Linux Audit Framework.</description><pubDate>Thu, 25 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;auditd&lt;/h1&gt;
&lt;h2&gt;Configuration File&lt;/h2&gt;
&lt;p&gt;The main configuration file for auditd is located at:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/audit/auditd.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file controls the behavior of auditd itself, including log writing, log rotation, and how auditd responds to disk space shortages or errors.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Log writing behavior
flush = INCREMENTAL
freq = 20

# Log rotation
num_logs = 5
max_log_file = 6
max_log_file_action = ROTATE

# Dispatcher
disp_qos = lossy
dispatcher = /sbin/audispd
name_format = NONE

# Disk space threshold
space_left = 75

# The &quot;_action&quot; options determine how errors or disk space issues should be handled:
admin_space_left_action = SUSPEND
disk_full_action = SUSPEND
disk_error_action = SUSPEND
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max_log_file&lt;/code&gt; is measured in &lt;strong&gt;MB&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;num_logs&lt;/code&gt; only takes effect when &lt;code&gt;max_log_file_action&lt;/code&gt; is set to &lt;code&gt;ROTATE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Modern Linux distributions (such as RHEL 9 and Ubuntu 24.04) have integrated the functionality of &lt;code&gt;audispd&lt;/code&gt; into &lt;code&gt;auditd&lt;/code&gt;. As a result, some systems no longer run a separate &lt;code&gt;audispd&lt;/code&gt; process, although the configuration option may still exist for compatibility.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Audit Rules&lt;/h2&gt;
&lt;p&gt;Audit Rules can be divided into three categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Control Rules&lt;/strong&gt; — Control the behavior of the Linux Audit Framework.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;File Watch Rules&lt;/strong&gt; (also referred to as &lt;strong&gt;File System Rules&lt;/strong&gt;) — Audit specific files or directories.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;System Call Rules&lt;/strong&gt; — Audit specific system calls.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Modern Linux distributions typically recommend placing custom rules under:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/audit/rules.d/*.rules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;augenrules --load
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to automatically generate and load:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/audit/audit.rules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although editing &lt;code&gt;audit.rules&lt;/code&gt; directly is still supported, using the &lt;code&gt;rules.d&lt;/code&gt; directory is the recommended approach because it is easier to maintain and manage.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Example Rules&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# ----------------------------
# Control Rules
# ----------------------------

# Delete all existing rules
-D

# Kernel backlog queue size
## Increase the buffers to survive stress events.
## Make this bigger for busy systems
-b 8192

# Set failure mode
# 0 = silent
# 1 = printk
# 2 = panic
-f 2


# ----------------------------
# File Watch Rules
# ----------------------------

-w /etc/passwd  -p wa -k passwd_changes
-w /etc/group   -p wa -k group_changes
-w /etc/shadow  -p wa -k shadow_changes
-w /etc/sudoers -p wa -k sudoers_changes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Watch a specific file or directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Permission types (&lt;code&gt;r&lt;/code&gt;, &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Assign a key to the rule for easier searching with &lt;code&gt;ausearch -k&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Permission flags:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute Change (such as &lt;code&gt;chmod&lt;/code&gt;, &lt;code&gt;chown&lt;/code&gt;, and &lt;code&gt;touch&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ausearch -k passwd_changes
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;Further Reading: What is auditd?&lt;/h1&gt;
&lt;p&gt;In Linux, almost every security-related operation is ultimately handled by the &lt;strong&gt;Kernel&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User login or logout&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;sudo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Executing a program&lt;/li&gt;
&lt;li&gt;Modifying &lt;code&gt;/etc/passwd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Changing file permissions (&lt;code&gt;chmod&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Deleting files&lt;/li&gt;
&lt;li&gt;Creating new processes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These operations are eventually translated into one or more &lt;strong&gt;System Calls&lt;/strong&gt;, which are processed by the Linux Kernel.&lt;/p&gt;
&lt;p&gt;To support security auditing and compliance requirements, the Linux Kernel provides a built-in &lt;strong&gt;Linux Audit Framework&lt;/strong&gt; that records security-related events.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;auditd (Linux Audit Daemon)&lt;/strong&gt; is the userspace daemon that works with this framework.&lt;/p&gt;
&lt;p&gt;Its responsibilities include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Receiving Audit Events generated by the Kernel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Writing events to &lt;code&gt;/var/log/audit/audit.log&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Managing log rotation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Forwarding events to other plugins or logging systems (such as rsyslog or a SIEM platform)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The workflow can be summarized as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;System Call / File Change
            │
            ▼
Linux Kernel (Audit Framework)
            │
      Generate Audit Event
            │
            ▼
         auditd
            │
            ▼
/var/log/audit/audit.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In general, the process works like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The administrator loads Audit Rules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Linux Kernel monitors system activity according to those rules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When a rule is matched, the Kernel generates an &lt;strong&gt;Audit Event&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;auditd receives the event and writes it to &lt;code&gt;/var/log/audit/audit.log&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Administrators use tools such as &lt;code&gt;ausearch&lt;/code&gt; and &lt;code&gt;aureport&lt;/code&gt; to search or analyze the logs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The responsibilities of each component are summarized below:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux Audit Framework&lt;/td&gt;
&lt;td&gt;A kernel subsystem that generates Audit Events based on configured Audit Rules.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auditd&lt;/td&gt;
&lt;td&gt;Receives Audit Events and writes them to audit logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auditctl&lt;/td&gt;
&lt;td&gt;Dynamically manages Audit Rules stored in the Kernel.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;augenrules&lt;/td&gt;
&lt;td&gt;Merges and loads rules from &lt;code&gt;/etc/audit/rules.d/&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ausearch&lt;/td&gt;
&lt;td&gt;Searches Audit Logs based on specified criteria.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aureport&lt;/td&gt;
&lt;td&gt;Generates summary reports from Audit Logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Understanding this architecture makes it easier to explain several common behaviors.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why do changes made with &lt;code&gt;auditctl&lt;/code&gt; take effect immediately?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because &lt;code&gt;auditctl&lt;/code&gt; updates the Audit Rules stored in the Kernel directly, rather than modifying configuration files.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why do rules disappear after a reboot?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because rules added with &lt;code&gt;auditctl&lt;/code&gt; exist only in memory. They must be reloaded from configuration files after the system starts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why can the Kernel continue generating Audit Events even if auditd is stopped?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Because auditing is performed by the Linux Kernel. Without auditd, there is simply no userspace daemon available to receive and write those events to disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simple way to remember the Linux Audit System is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Audit Rules tell the Kernel what to monitor; the Kernel detects events; auditd records them; and tools like &lt;code&gt;ausearch&lt;/code&gt; and &lt;code&gt;aureport&lt;/code&gt; help you analyze them.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Linux auditd 使用笔记</title><link>https://blog.drcharon.com/posts/auditd-note/zh/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/auditd-note/zh/</guid><description>记录 auditd 的基本配置、审计规则（Audit Rules）以及常用示例，并简要介绍 Linux Audit Framework 的工作原理。</description><pubDate>Thu, 25 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;auditd&lt;/h1&gt;
&lt;h2&gt;配置文件&lt;/h2&gt;
&lt;p&gt;auditd 的主配置文件位于：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/audit/auditd.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用于控制 auditd 自身的行为，例如日志写入方式、日志轮转以及磁盘空间不足时的处理策略。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Log writing behavior
flush = INCREMENTAL
freq = 20

# Log rotation
num_logs = 5
max_log_file = 6
max_log_file_action = ROTATE

# Dispatcher
disp_qos = lossy
dispatcher = /sbin/audispd
name_format = NONE

# Disk space threshold
space_left = 75

#The &quot;_action&quot; options determine how errors or disk space issues should be handled:
admin_space_left_action = SUSPEND
disk_full_action = SUSPEND
disk_error_action = SUSPEND
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;max_log_file&lt;/code&gt; 的单位为 &lt;strong&gt;MB&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;num_logs&lt;/code&gt; 仅在 &lt;code&gt;max_log_file_action = ROTATE&lt;/code&gt; 时生效。&lt;/li&gt;
&lt;li&gt;较新的 Linux 发行版（如 RHEL 9、Ubuntu 24.04）已将 &lt;code&gt;audispd&lt;/code&gt; 的功能整合到 &lt;code&gt;auditd&lt;/code&gt; 中，因此部分系统可能没有独立的 &lt;code&gt;audispd&lt;/code&gt; 进程，但配置项仍可能保留用于兼容。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Audit Rules&lt;/h2&gt;
&lt;p&gt;Audit Rule 可分为三种类型：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Control Rules&lt;/strong&gt; —— 控制 Auditd 的行为。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File Watch Rules&lt;/strong&gt;（File System Rules）—— 审计指定文件或目录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System Call Rules&lt;/strong&gt; —— 审计指定的 System Call。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现代 Linux 通常建议将自定义规则放在：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/audit/rules.d/*.rules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;augenrules --load
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自动生成并加载：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/etc/audit/audit.rules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然仍然可以直接编辑 &lt;code&gt;audit.rules&lt;/code&gt;，但官方更推荐使用 &lt;code&gt;rules.d&lt;/code&gt; 的方式，便于维护和版本管理。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;示例规则&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# ----------------------------
# Control Rules
# ----------------------------

# Delete all existing rules
-D

# Kernel backlog queue size
## Increase the buffers to survive stress events. 
## Make this bigger for busy systems
-b 8192

## Set failure mode to panic
# 0 = silent
# 1 = printk
# 2 = panic
-f 2


# ----------------------------
# File Watch Rules
# ----------------------------

-w /etc/passwd  -p wa -k passwd_changes
-w /etc/group   -p wa -k group_changes
-w /etc/shadow  -p wa -k shadow_changes
-w /etc/sudoers -p wa -k sudoers_changes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;监控指定文件或目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;权限类型（r、w、x、a）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;为规则添加 Key，方便使用 &lt;code&gt;ausearch -k&lt;/code&gt; 搜索&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;权限说明：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Execute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Attribute Change（如 chmod、chown、touch 等）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ausearch -k passwd_changes
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;补充阅读：什么是 auditd？&lt;/h1&gt;
&lt;p&gt;在 Linux 中，几乎所有与安全相关的操作最终都会经过 &lt;strong&gt;Kernel（内核）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户登录或注销&lt;/li&gt;
&lt;li&gt;执行 &lt;code&gt;sudo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;启动一个程序&lt;/li&gt;
&lt;li&gt;修改 &lt;code&gt;/etc/passwd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;修改文件权限（&lt;code&gt;chmod&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;删除文件&lt;/li&gt;
&lt;li&gt;创建新的进程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些操作最终都会转换成一个或多个 &lt;strong&gt;System Call（系统调用）&lt;/strong&gt;，由 Linux Kernel 处理。
为了满足安全审计（Security Auditing）和合规（Compliance）的需求，Linux Kernel 内置了一套 &lt;strong&gt;Linux Audit Framework&lt;/strong&gt;，用于记录这些安全相关事件。
而 &lt;strong&gt;auditd（Linux Audit Daemon）&lt;/strong&gt;，则是这套框架对应的用户空间（Userspace）守护进程。&lt;/p&gt;
&lt;p&gt;它负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接收 Kernel 发送的 Audit Event&lt;/li&gt;
&lt;li&gt;将事件写入 &lt;code&gt;/var/log/audit/audit.log&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;管理日志轮转（Log Rotation）&lt;/li&gt;
&lt;li&gt;将事件转发给其他插件或日志系统（如 rsyslog、SIEM）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个流程可以理解为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;System Call / File Change
            │
            ▼
Linux Kernel (Audit Framework)
            │
      Generate Audit Event
            │
            ▼
         auditd
            │
            ▼
/var/log/audit/audit.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;整个过程中，可以简单理解为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;管理员加载 Audit Rules。&lt;/li&gt;
&lt;li&gt;Linux Kernel 根据这些规则监控系统行为。&lt;/li&gt;
&lt;li&gt;当发生匹配事件时，Kernel 生成一个 &lt;strong&gt;Audit Event&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;auditd 接收该事件，并写入 &lt;code&gt;/var/log/audit/audit.log&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;管理员使用 &lt;code&gt;ausearch&lt;/code&gt;、&lt;code&gt;aureport&lt;/code&gt; 等工具查询或分析日志。&lt;/li&gt;
&lt;li&gt;因此，各组件的职责分别是：&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux Audit Framework&lt;/td&gt;
&lt;td&gt;Kernel 内置的审计框架，负责根据规则产生 Audit Event。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auditd&lt;/td&gt;
&lt;td&gt;接收 Audit Event，并保存到日志。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auditctl&lt;/td&gt;
&lt;td&gt;动态管理 Kernel 中的 Audit Rules。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;augenrules&lt;/td&gt;
&lt;td&gt;将 &lt;code&gt;/etc/audit/rules.d/&lt;/code&gt; 中的规则合并并加载。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ausearch&lt;/td&gt;
&lt;td&gt;根据条件搜索 Audit Log。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aureport&lt;/td&gt;
&lt;td&gt;根据 Audit Log 生成统计报告。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;理解这一点之后，很多现象就变得容易解释了。 例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;为什么 &lt;code&gt;auditctl&lt;/code&gt; 修改规则后立即生效？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因为它直接修改的是 Kernel 中的 Audit Rules，而不是修改配置文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为什么重启之后规则消失？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因为 &lt;code&gt;auditctl&lt;/code&gt; 修改的是运行时规则，重启后需要重新从配置文件加载。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为什么 auditd 停止以后，系统依然可能继续产生 Audit Event？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因为真正负责审计的是 Linux Kernel，只是没有用户空间进程继续接收并写入日志。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以把整个 Linux Audit System 理解成一句话： &amp;gt; &lt;strong&gt;Audit Rules 告诉 Kernel 要监控什么；Kernel 负责发现事件；auditd 负责保存事件；ausearch 和 aureport 负责分析事件。&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>Expressive Code Example</title><link>https://blog.drcharon.com/posts/expressive-code/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/expressive-code/</guid><description>How code blocks look in Markdown using Expressive Code.</description><pubDate>Wed, 10 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here, we&apos;ll explore how code blocks look using &lt;a href=&quot;https://expressive-code.com/&quot;&gt;Expressive Code&lt;/a&gt;. The provided examples are based on the official documentation, which you can refer to for further details.&lt;/p&gt;
&lt;h2&gt;Expressive Code&lt;/h2&gt;
&lt;h3&gt;Syntax Highlighting&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/syntax-highlighting/&quot;&gt;Syntax Highlighting&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Regular syntax highlighting&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;This code is syntax highlighted!&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Rendering ANSI escape sequences&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ANSI colors:
- Regular: [31mRed[0m [32mGreen[0m [33mYellow[0m [34mBlue[0m [35mMagenta[0m [36mCyan[0m
- Bold:    [1;31mRed[0m [1;32mGreen[0m [1;33mYellow[0m [1;34mBlue[0m [1;35mMagenta[0m [1;36mCyan[0m
- Dimmed:  [2;31mRed[0m [2;32mGreen[0m [2;33mYellow[0m [2;34mBlue[0m [2;35mMagenta[0m [2;36mCyan[0m

256 colors (showing colors 160-177):
[38;5;160m160 [38;5;161m161 [38;5;162m162 [38;5;163m163 [38;5;164m164 [38;5;165m165[0m
[38;5;166m166 [38;5;167m167 [38;5;168m168 [38;5;169m169 [38;5;170m170 [38;5;171m171[0m
[38;5;172m172 [38;5;173m173 [38;5;174m174 [38;5;175m175 [38;5;176m176 [38;5;177m177[0m

Full RGB colors:
[38;2;34;139;34mForestGreen - RGB(34, 139, 34)[0m

Text formatting: [1mBold[0m [2mDimmed[0m [3mItalic[0m [4mUnderline[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Editor &amp;amp; Terminal Frames&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/frames/&quot;&gt;Editor &amp;amp; Terminal Frames&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Code editor frames&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Title attribute example&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- src/content/index.html --&amp;gt;
&amp;lt;div&amp;gt;File name comment example&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Terminal frames&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;This terminal frame has no title&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;Write-Output &quot;This one has a title!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Overriding frame types&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Look ma, no frame!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;# Without overriding, this would be a terminal frame
function Watch-Tail { Get-Content -Tail 20 -Wait $args }
New-Alias tail Watch-Tail
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Text &amp;amp; Line Markers&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/text-markers/&quot;&gt;Text &amp;amp; Line Markers&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Marking full lines &amp;amp; line ranges&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Line 1 - targeted by line number
// Line 2
// Line 3
// Line 4 - targeted by line number
// Line 5
// Line 6
// Line 7 - targeted by range &quot;7-8&quot;
// Line 8 - targeted by range &quot;7-8&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Selecting line marker types (mark, ins, del)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;this line is marked as deleted&apos;)
  // This line and the next one are marked as inserted
  console.log(&apos;this is the second inserted line&apos;)

  return &apos;this line uses the neutral default marker type&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding labels to line markers&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}
  value={value}
  className={buttonClassName}
  disabled={disabled}
  active={active}
&amp;gt;
  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding long labels on their own lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}

  value={value}
  className={buttonClassName}

  disabled={disabled}
  active={active}
&amp;gt;

  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Using diff-like syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;+this line will be marked as inserted
-this line will be marked as deleted
this is a regular line
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+this is an actual diff file
-all contents will remain unmodified
 no whitespace will be removed either
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Combining syntax highlighting with diff-like syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;  function thisIsJavaScript() {
    // This entire block gets highlighted as JavaScript,
    // and we can still add diff markers to it!
-   console.log(&apos;Old code to be removed&apos;)
+   console.log(&apos;New and shiny code!&apos;)
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Marking individual text inside lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  // Mark any given text inside lines
  return &apos;Multiple matches of the given text are supported&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Regular expressions&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;The words yes and yep will be marked.&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Escaping forward slashes&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Test&quot; &amp;gt; /home/test.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Selecting inline marker types (mark, ins, del)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;These are inserted and deleted marker types&apos;);
  // The return statement uses the default marker type
  return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Word Wrap&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/word-wrap/&quot;&gt;Word Wrap&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Configuring word wrap per block&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Example with wrap
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Example with wrap=false
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Configuring indentation of wrapped lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Example with preserveIndent (enabled by default)
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Example with preserveIndent=false
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Collapsible Sections&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/collapsible-sections/&quot;&gt;Collapsible Sections&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// All this boilerplate setup code will be collapsed
import { someBoilerplateEngine } from &apos;@example/some-boilerplate&apos;
import { evenMoreBoilerplate } from &apos;@example/even-more-boilerplate&apos;

const engine = someBoilerplateEngine(evenMoreBoilerplate())

// This part of the code will be visible by default
engine.doSomething(1, 2, 3, calcFn)

function calcFn() {
  // You can have multiple collapsed sections
  const a = 1
  const b = 2
  const c = a + b

  // This will remain visible
  console.log(`Calculation result: ${a} + ${b} = ${c}`)
  return c
}

// All this code until the end of the block will be collapsed again
engine.closeConnection()
engine.freeMemory()
engine.shutdown({ reason: &apos;End of example boilerplate code&apos; })
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Line Numbers&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/line-numbers/&quot;&gt;Line Numbers&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Displaying line numbers per block&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// This code block will show line numbers
console.log(&apos;Greetings from line 2!&apos;)
console.log(&apos;I am on line 3&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Line numbers are disabled for this block
console.log(&apos;Hello?&apos;)
console.log(&apos;Sorry, do you know what line I am on?&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Changing the starting line number&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Greetings from line 5!&apos;)
console.log(&apos;I am on line 6&apos;)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Simple Guides for Fuwari</title><link>https://blog.drcharon.com/posts/guide/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/guide/</guid><description>How to use this blog template.</description><pubDate>Mon, 01 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image source: &lt;a href=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This blog template is built with &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. For the things that are not mentioned in this guide, you may find the answers in the &lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro Docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Front-matter of Posts&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The title of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;published&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The date the post was published.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A short description of the post. Displayed on index page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The cover image path of the post.&amp;lt;br/&amp;gt;1. Start with &lt;code&gt;http://&lt;/code&gt; or &lt;code&gt;https://&lt;/code&gt;: Use web image&amp;lt;br/&amp;gt;2. Start with &lt;code&gt;/&lt;/code&gt;: For image in &lt;code&gt;public&lt;/code&gt; dir&amp;lt;br/&amp;gt;3. With none of the prefixes: Relative to the markdown file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The tags of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The category of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;draft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If this post is still a draft, which won&apos;t be displayed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Where to Place the Post Files&lt;/h2&gt;
&lt;p&gt;Your post files should be placed in &lt;code&gt;src/content/posts/&lt;/code&gt; directory. You can also create sub-directories to better organize your posts and assets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/content/posts/
├── post-1.md
└── post-2/
    ├── cover.png
    └── index.md
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Markdown Example</title><link>https://blog.drcharon.com/posts/markdown/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/markdown/</guid><description>A simple example of a Markdown blog post.</description><pubDate>Sun, 01 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;An h1 header&lt;/h1&gt;
&lt;p&gt;Paragraphs are separated by a blank line.&lt;/p&gt;
&lt;p&gt;2nd paragraph. &lt;em&gt;Italic&lt;/em&gt;, &lt;strong&gt;bold&lt;/strong&gt;, and &lt;code&gt;monospace&lt;/code&gt;. Itemized lists
look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;this one&lt;/li&gt;
&lt;li&gt;that one&lt;/li&gt;
&lt;li&gt;the other one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Block quotes are
written like so.&lt;/p&gt;
&lt;p&gt;They can span multiple paragraphs,
if you like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., &quot;it&apos;s all
in chapters 12--14&quot;). Three dots ... will be converted to an ellipsis.
Unicode is supported. ☺&lt;/p&gt;
&lt;h2&gt;An h2 header&lt;/h2&gt;
&lt;p&gt;Here&apos;s a numbered list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;first item&lt;/li&gt;
&lt;li&gt;second item&lt;/li&gt;
&lt;li&gt;third item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here&apos;s a code sample:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;define foobar() {
    print &quot;Welcome to flavor country!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(which makes copying &amp;amp; pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import time
# Quick, count to ten!
for i in range(10):
    # (but not *too* quick)
    time.sleep(0.5)
    print i
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;An h3 header&lt;/h3&gt;
&lt;p&gt;Now a nested list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, get these ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;carrots&lt;/li&gt;
&lt;li&gt;celery&lt;/li&gt;
&lt;li&gt;lentils&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Boil some water.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dump everything in the pot and follow
this algorithm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; find wooden spoon
 uncover pot
 stir
 cover pot
 balance wooden spoon precariously on pot handle
 wait 10 minutes
 goto first step (or shut off burner when done)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not bump wooden spoon or it will fall.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).&lt;/p&gt;
&lt;p&gt;Here&apos;s a link to &lt;a href=&quot;http://foo.bar&quot;&gt;a website&lt;/a&gt;, to a &lt;a href=&quot;local-doc.html&quot;&gt;local
doc&lt;/a&gt;, and to a &lt;a href=&quot;#an-h2-header&quot;&gt;section heading in the current
doc&lt;/a&gt;. Here&apos;s a footnote [^1].&lt;/p&gt;
&lt;p&gt;[^1]: Footnote text goes here.&lt;/p&gt;
&lt;p&gt;Tables can look like this:&lt;/p&gt;
&lt;p&gt;size material color&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;9 leather brown
10 hemp canvas natural
11 glass transparent&lt;/p&gt;
&lt;p&gt;Table: Shoes, their sizes, and what they&apos;re made of&lt;/p&gt;
&lt;p&gt;(The above is the caption for the table.) Pandoc also supports
multi-line tables:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;keyword text&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;red Sunsets, apples, and
other red or reddish
things.&lt;/p&gt;
&lt;p&gt;green Leaves, grass, frogs
and other things it&apos;s
not easy being.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;A horizontal rule follows.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Here&apos;s a definition list:&lt;/p&gt;
&lt;p&gt;apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There&apos;s no &quot;e&quot; in tomatoe.&lt;/p&gt;
&lt;p&gt;Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)&lt;/p&gt;
&lt;p&gt;Here&apos;s a &quot;line block&quot;:&lt;/p&gt;
&lt;p&gt;| Line one
| Line too
| Line tree&lt;/p&gt;
&lt;p&gt;and images can be specified like so:&lt;/p&gt;
&lt;p&gt;Inline math equations go in like so: $\omega = d\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:&lt;/p&gt;
&lt;p&gt;$$I = \int \rho R^{2} dV$$&lt;/p&gt;
&lt;p&gt;$$
\begin{equation*}
\pi
=3.1415926535
;8979323846;2643383279;5028841971;6939937510;5820974944
;5923078164;0628620899;8628034825;3421170679;\ldots
\end{equation*}
$$&lt;/p&gt;
&lt;p&gt;And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: `foo`, *bar*, etc.&lt;/p&gt;
</content:encoded></item><item><title>Include Video in the Posts</title><link>https://blog.drcharon.com/posts/video/</link><guid isPermaLink="true">https://blog.drcharon.com/posts/video/</guid><description>This post demonstrates how to include embedded video in a blog post.</description><pubDate>Tue, 01 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Include Video in the Post
published: 2023-10-19
// ...
---

&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;YouTube&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Bilibili&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item></channel></rss>