Delphi获取Windows防火墙出站规则名称:疑难解答与方案
2025-03-09 03:29:13
Windows 防火墙出站规则名称获取:疑难解答与解决方案
最近在写一个功能,需要获取本机所有防火墙的出站规则名称,却发现代码一直报错,无法正常运行。 原本的代码如下:
function GetOutboundFirewallRuleNames: TStringList;
var
i: Integer;
begin
var ruleNames := TStringList.Create;
// Initialize COM
CoInitialize(nil);
try
try
// Create an instance of the firewall policy
var fwPolicy2: OleVariant := CreateOleObject('HNetCfg.FwPolicy2');
// Loop through all rules to find the outbound rules
for i := 1 to fwPolicy2.Rules.Count do
begin
var rule: OleVariant := fwPolicy2.Rules.Item(i - 1);
if rule.Direction = 2 then // Outbound direction
begin
ruleNames.Add(rule.Name);
end;
end;
Result := ruleNames;
except
on E: EOleException do
begin
ruleNames.Free;
raise Exception.CreateFmt('Error accessing firewall rules: %s', [E.Message]);
end;
on E: Exception do
begin
ruleNames.Free;
raise;
end;
end;
finally
// Uninitialize COM
CoUninitialize;
end;
end;
运行后,遇到以下错误:
Project raised exception class EOleException with message 'The system cannot find the file specified'.
Project raised exception class Exception with message 'Error accessing firewall rules: The system cannot find the file specified'.
错误似乎发生在这一行:
var rule: OleVariant := fwPolicy2.Rules.Item(i - 1);
即使我用管理员权限运行 Delphi IDE 和编译后的程序,问题依旧。 挺让人头疼的。
问题原因分析
错误信息 "The system cannot find the file specified" 通常和 COM 组件的注册、访问权限,或者防火墙服务本身的状态有关。 经过一番排查, 可能性比较大的原因包括:
- 防火墙服务未运行: Windows 防火墙服务 (MpsSvc) 可能未启动,或者被禁用。
- COM 组件问题:
HNetCfg.FwPolicy2
COM 对象可能未正确注册,或其相关 DLL 文件缺失、损坏。 这种概率较低。 - 索引越界:
fwPolicy2.Rules.Item(i - 1)
中的索引可能存在问题。在某些情况下,防火墙规则集合的索引可能不是从 0 或 1 开始的连续整数,或者Count
属性的值不准确,导致访问越界。 - 代码写法问题 :
fwPolicy2.Rules
枚举规则, 是个较为复杂的过程,直接用 Item 下标索引很容易出错.
解决方案
针对以上可能的原因,我们可以尝试以下几种解决方案。
1. 检查防火墙服务状态
最简单的,先确认防火墙服务是不是开着的。
操作步骤:
- 按下
Win + R
键,打开“运行”对话框。 - 输入
services.msc
,然后点击“确定”。 - 找到 “Windows Defender Firewall” 或 "Windows Firewall" 服务。
- 检查其状态是否为“正在运行”。如果不是,右键点击该服务,选择“启动”。
- 确保“启动类型”设置为“自动”。
2. 使用 for…in
循环遍历防火墙规则 (推荐)
与其通过索引访问防火墙规则,不如直接用 for...in
循环遍历,这样更安全可靠,可以避免索引越界的问题。
原理:
for...in
循环可以直接遍历 COM 集合对象,无需手动管理索引。
代码示例:
function GetOutboundFirewallRuleNames: TStringList;
var
ruleNames: TStringList;
fwPolicy2: OleVariant;
rule: OleVariant;
begin
ruleNames := TStringList.Create;
// 初始化 COM
CoInitialize(nil);
try
try
// 创建防火墙策略实例
fwPolicy2 := CreateOleObject('HNetCfg.FwPolicy2');
// 使用 for...in 循环遍历所有规则
for rule in fwPolicy2.Rules do
begin
// 检查是否为出站规则 (Direction = 2)
if rule.Direction = 2 then
begin
ruleNames.Add(rule.Name);
end;
end;
Result := ruleNames;
except
on E: EOleException do
begin
ruleNames.Free;
raise Exception.CreateFmt('访问防火墙规则时出错: %s', [E.Message]);
end;
on E: Exception do
begin
ruleNames.Free;
raise;
end;
end;
finally
// 取消初始化 COM
CoUninitialize;
end;
end;
改进说明:
这段代码直接使用 for rule in fwPolicy2.Rules
遍历规则,避免了手动索引可能导致的错误。 更简洁,也更安全。
3. 使用 NetFwTypeLib
(更 Delphi 原生)
如果想用更贴近 Delphi 原生类型的方式操作防火墙,可以使用 NetFwTypeLib
单元。 它提供了对防火墙 API 的类型安全封装。
原理:
NetFwTypeLib
提供了强类型的接口,避免了使用 OleVariant
,代码可读性更高,编译时也能发现更多潜在的类型错误。
代码示例:
uses
..., NetFwTypeLib;
function GetOutboundFirewallRuleNames: TStringList;
var
ruleNames: TStringList;
FwMgr: INetFwMgr;
Rules: INetFwRules;
Rule: INetFwRule;
I: Integer;
begin
ruleNames := TStringList.Create;
FwMgr := CoNetFwMgr.Create;
if not Assigned(FwMgr.LocalPolicy.CurrentProfile) then
raise Exception.Create('无法获取当前防火墙配置文件');
Rules := FwMgr.LocalPolicy.CurrentProfile.Rules;
for I := 0 to Rules.Count - 1 do
begin
Rule := Rules.Item(I);
if Rule.Direction = NET_FW_RULE_DIR_OUT then
begin
ruleNames.Add(Rule.Name);
end;
end;
Result := ruleNames;
end;
改进说明:
这段代码比上面的更严谨, 可以减少很多运行出错的概率.
4. 使用命令行 (PowerShell)
如果不一定要在 Delphi 代码里实现,用 PowerShell 命令会更简单直接。
原理:
PowerShell 的 Get-NetFirewallRule
cmdlet 可以轻松获取防火墙规则,-Direction Outbound
参数用于筛选出站规则。
命令行指令:
Get-NetFirewallRule -Direction Outbound | Select-Object -ExpandProperty Name
这个命令直接列出所有出站防火墙规则的名称,一行一个。非常方便。
也可以把这个命令封装到Delphi代码里面.
代码示例:
uses
System.SysUtils, System.Classes, Winapi.Windows;
function ExecutePowerShellCommand(const ACommand: string): string;
var
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
SecurityAttr: TSecurityAttributes;
ReadPipe, WritePipe: THandle;
Buffer: array[0..4095] of Char;
BytesRead: DWORD;
Output: string;
begin
// 设置管道安全性
SecurityAttr.nLength := SizeOf(TSecurityAttributes);
SecurityAttr.bInheritHandle := True;
SecurityAttr.lpSecurityDescriptor := nil;
// 创建匿名管道
if not CreatePipe(ReadPipe, WritePipe, @SecurityAttr, 0) then
begin
Exit;
end;
try
// 设置启动信息
FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
StartupInfo.cb := SizeOf(TStartupInfo);
StartupInfo.hStdError := WritePipe;
StartupInfo.hStdOutput := WritePipe;
StartupInfo.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := SW_HIDE; // 隐藏窗口
// 构建 PowerShell 命令
var CmdLine := 'powershell.exe -ExecutionPolicy Bypass -Command "' + ACommand + '"';
// 创建进程
if CreateProcess(nil, PChar(CmdLine), nil, nil, True,
CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,
nil, nil, StartupInfo, ProcessInfo) then
begin
try
// 关闭写管道的句柄 (在子进程中)
CloseHandle(WritePipe);
WritePipe := 0;
// 读取管道输出
while True do
begin
if not ReadFile(ReadPipe, Buffer, SizeOf(Buffer) - 1, BytesRead, nil) then
Break;
if BytesRead = 0 then
Break;
Buffer[BytesRead] := #0;
Output := Output + string(Buffer);
FillChar(Buffer, SizeOf(Buffer), 0);
end;
// 等待进程结束
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
finally
// 关闭进程和线程句柄
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
end;
end;
finally
// 关闭管道句柄
if ReadPipe <> 0 then
CloseHandle(ReadPipe);
if WritePipe <> 0 then
CloseHandle(WritePipe);
end;
Result := Output;
end;
function GetOutboundFirewallRules: TStringList;
var
s: string;
tempsl : TStringList;
begin
tempsl := TStringList.Create;
s := ExecutePowerShellCommand('Get-NetFirewallRule -Direction Outbound | Select-Object -ExpandProperty Name');
tempsl.Text := s;
result := tempsl;
end;
改进说明
这个版本使用了Delphi 调用 Powershell 命令的方法. 代码复杂了一点,但是适用范围更广.
5. 高级调试(COM 接口错误)
如果确认服务没问题,但还是报错,且坚持要用原始的 COM 接口方法,那可以尝试以下方法。
(但是请优先使用for...in
方法)
-
重新注册 COM 组件:
以管理员身份运行命令提示符,然后运行以下命令:
regsvr32.exe /u FirewallAPI.dll regsvr32.exe FirewallAPI.dll
然后重启下电脑, 再试试看.
-
用调试器逐步调试代码,查看
fwPolicy2.Rules.Count
的值是否正确, 查看在调用fwPolicy2.Rules.Item(i-1)
之前,i
的值是否正确.
总结
获取 Windows 防火墙出站规则名称,PowerShell 一行命令就能搞定。如果在 Delphi 里实现,推荐用 for...in
循环遍历 fwPolicy2.Rules
,或者使用 NetFwTypeLib
,代码更清晰、更安全。 原始代码的问题,很可能是索引的起始和递增的方式和预期有差别, 或者是Count
属性的值不准确。