返回

Delphi获取Windows防火墙出站规则名称:疑难解答与方案

windows

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 组件的注册、访问权限,或者防火墙服务本身的状态有关。 经过一番排查, 可能性比较大的原因包括:

  1. 防火墙服务未运行: Windows 防火墙服务 (MpsSvc) 可能未启动,或者被禁用。
  2. COM 组件问题: HNetCfg.FwPolicy2 COM 对象可能未正确注册,或其相关 DLL 文件缺失、损坏。 这种概率较低。
  3. 索引越界: fwPolicy2.Rules.Item(i - 1) 中的索引可能存在问题。在某些情况下,防火墙规则集合的索引可能不是从 0 或 1 开始的连续整数,或者Count 属性的值不准确,导致访问越界。
  4. 代码写法问题fwPolicy2.Rules 枚举规则, 是个较为复杂的过程,直接用 Item 下标索引很容易出错.

解决方案

针对以上可能的原因,我们可以尝试以下几种解决方案。

1. 检查防火墙服务状态

最简单的,先确认防火墙服务是不是开着的。

操作步骤:

  1. 按下 Win + R 键,打开“运行”对话框。
  2. 输入 services.msc,然后点击“确定”。
  3. 找到 “Windows Defender Firewall” 或 "Windows Firewall" 服务。
  4. 检查其状态是否为“正在运行”。如果不是,右键点击该服务,选择“启动”。
  5. 确保“启动类型”设置为“自动”。

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 属性的值不准确。